]> git.ipfire.org Git - thirdparty/kernel/linux.git/commitdiff
bit_spinlock: Support Clang's context analysis
authorMarco Elver <elver@google.com>
Fri, 19 Dec 2025 15:40:02 +0000 (16:40 +0100)
committerPeter Zijlstra <peterz@infradead.org>
Mon, 5 Jan 2026 15:43:30 +0000 (16:43 +0100)
The annotations for bit_spinlock.h have simply been using "bitlock" as
the token. For Sparse, that was likely sufficient in most cases. But
Clang's context analysis is more precise, and we need to ensure we
can distinguish different bitlocks.

To do so, add a token context, and a macro __bitlock(bitnum, addr)
that is used to construct unique per-bitlock tokens.

Add the appropriate test.

<linux/list_bl.h> is implicitly included through other includes, and
requires 2 annotations to indicate that acquisition (without release)
and release (without prior acquisition) of its bitlock is intended.

Signed-off-by: Marco Elver <elver@google.com>
Signed-off-by: Peter Zijlstra (Intel) <peterz@infradead.org>
Link: https://patch.msgid.link/20251219154418.3592607-14-elver@google.com
Documentation/dev-tools/context-analysis.rst
include/linux/bit_spinlock.h
include/linux/list_bl.h
lib/test_context-analysis.c

index 690565910084b34c2de0073ed34c62467e0d1ad4..b2d69fb4a884777721618ab880d41efa84df6f86 100644 (file)
@@ -79,7 +79,8 @@ Supported Kernel Primitives
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
 Currently the following synchronization primitives are supported:
-`raw_spinlock_t`, `spinlock_t`, `rwlock_t`, `mutex`, `seqlock_t`.
+`raw_spinlock_t`, `spinlock_t`, `rwlock_t`, `mutex`, `seqlock_t`,
+`bit_spinlock`.
 
 For context locks with an initialization function (e.g., `spin_lock_init()`),
 calling this function before initializing any guarded members or globals
index 59e345f74b0e2f31ffa43656be0a4233d75b17b2..7869a6e59b6a6cc29acc41f0189f1b975b7787c4 100644 (file)
@@ -9,6 +9,16 @@
 
 #include <asm/processor.h>  /* for cpu_relax() */
 
+/*
+ * For static context analysis, we need a unique token for each possible bit
+ * that can be used as a bit_spinlock. The easiest way to do that is to create a
+ * fake context that we can cast to with the __bitlock(bitnum, addr) macro
+ * below, which will give us unique instances for each (bit, addr) pair that the
+ * static analysis can use.
+ */
+context_lock_struct(__context_bitlock) { };
+#define __bitlock(bitnum, addr) (struct __context_bitlock *)(bitnum + (addr))
+
 /*
  *  bit-based spin_lock()
  *
@@ -16,6 +26,7 @@
  * are significantly faster.
  */
 static __always_inline void bit_spin_lock(int bitnum, unsigned long *addr)
+       __acquires(__bitlock(bitnum, addr))
 {
        /*
         * Assuming the lock is uncontended, this never enters
@@ -34,13 +45,14 @@ static __always_inline void bit_spin_lock(int bitnum, unsigned long *addr)
                preempt_disable();
        }
 #endif
-       __acquire(bitlock);
+       __acquire(__bitlock(bitnum, addr));
 }
 
 /*
  * Return true if it was acquired
  */
 static __always_inline int bit_spin_trylock(int bitnum, unsigned long *addr)
+       __cond_acquires(true, __bitlock(bitnum, addr))
 {
        preempt_disable();
 #if defined(CONFIG_SMP) || defined(CONFIG_DEBUG_SPINLOCK)
@@ -49,7 +61,7 @@ static __always_inline int bit_spin_trylock(int bitnum, unsigned long *addr)
                return 0;
        }
 #endif
-       __acquire(bitlock);
+       __acquire(__bitlock(bitnum, addr));
        return 1;
 }
 
@@ -57,6 +69,7 @@ static __always_inline int bit_spin_trylock(int bitnum, unsigned long *addr)
  *  bit-based spin_unlock()
  */
 static __always_inline void bit_spin_unlock(int bitnum, unsigned long *addr)
+       __releases(__bitlock(bitnum, addr))
 {
 #ifdef CONFIG_DEBUG_SPINLOCK
        BUG_ON(!test_bit(bitnum, addr));
@@ -65,7 +78,7 @@ static __always_inline void bit_spin_unlock(int bitnum, unsigned long *addr)
        clear_bit_unlock(bitnum, addr);
 #endif
        preempt_enable();
-       __release(bitlock);
+       __release(__bitlock(bitnum, addr));
 }
 
 /*
@@ -74,6 +87,7 @@ static __always_inline void bit_spin_unlock(int bitnum, unsigned long *addr)
  *  protecting the rest of the flags in the word.
  */
 static __always_inline void __bit_spin_unlock(int bitnum, unsigned long *addr)
+       __releases(__bitlock(bitnum, addr))
 {
 #ifdef CONFIG_DEBUG_SPINLOCK
        BUG_ON(!test_bit(bitnum, addr));
@@ -82,7 +96,7 @@ static __always_inline void __bit_spin_unlock(int bitnum, unsigned long *addr)
        __clear_bit_unlock(bitnum, addr);
 #endif
        preempt_enable();
-       __release(bitlock);
+       __release(__bitlock(bitnum, addr));
 }
 
 /*
index ae1b541446c906158cb057538b62241161e4b361..df9eebe6afca9cf32e60165ebf22a8adfe09947a 100644 (file)
@@ -144,11 +144,13 @@ static inline void hlist_bl_del_init(struct hlist_bl_node *n)
 }
 
 static inline void hlist_bl_lock(struct hlist_bl_head *b)
+       __acquires(__bitlock(0, b))
 {
        bit_spin_lock(0, (unsigned long *)b);
 }
 
 static inline void hlist_bl_unlock(struct hlist_bl_head *b)
+       __releases(__bitlock(0, b))
 {
        __bit_spin_unlock(0, (unsigned long *)b);
 }
index 53abea0008f2b3942c717949239bc3ea5bc51c89..be0c5d462a48aa3f3338b69825429fec72382b25 100644 (file)
@@ -4,6 +4,7 @@
  * positive errors when compiled with Clang's context analysis.
  */
 
+#include <linux/bit_spinlock.h>
 #include <linux/build_bug.h>
 #include <linux/mutex.h>
 #include <linux/seqlock.h>
@@ -258,3 +259,28 @@ static void __used test_seqlock_scoped(struct test_seqlock_data *d)
                (void)d->counter;
        }
 }
+
+struct test_bit_spinlock_data {
+       unsigned long bits;
+       int counter __guarded_by(__bitlock(3, &bits));
+};
+
+static void __used test_bit_spin_lock(struct test_bit_spinlock_data *d)
+{
+       /*
+        * Note, the analysis seems to have false negatives, because it won't
+        * precisely recognize the bit of the fake __bitlock() token.
+        */
+       bit_spin_lock(3, &d->bits);
+       d->counter++;
+       bit_spin_unlock(3, &d->bits);
+
+       bit_spin_lock(3, &d->bits);
+       d->counter++;
+       __bit_spin_unlock(3, &d->bits);
+
+       if (bit_spin_trylock(3, &d->bits)) {
+               d->counter++;
+               bit_spin_unlock(3, &d->bits);
+       }
+}