From: Peter Zijlstra <peterz@infradead.org>
To: "Puchert, Aaron" <aaron.puchert@sap.com>
Cc: Marco Elver <elver@google.com>,
Aaron Ballman <aaron@aaronballman.com>,
"linux-toolchains@vger.kernel.org"
<linux-toolchains@vger.kernel.org>,
"llvm@lists.linux.dev" <llvm@lists.linux.dev>,
Bart Van Assche <bvanassche@acm.org>,
"Paul E. McKenney" <paulmck@kernel.org>,
Boqun Feng <boqun.feng@gmail.com>
Subject: Re: Thread Safety Analysis and the Linux kernel
Date: Thu, 6 Mar 2025 10:47:52 +0100 [thread overview]
Message-ID: <20250306094752.GC16878@noisy.programming.kicks-ass.net> (raw)
In-Reply-To: <DB7PR02MB36260C79D8CD91925D0D3447E7CB2@DB7PR02MB3626.eurprd02.prod.outlook.com>
On Wed, Mar 05, 2025 at 11:54:14PM +0000, Puchert, Aaron wrote:
> > 2. Basic alias analysis, e.g. when storing a pointer to a lock in a
> > function-local variable. [Complaint in:
> > https://lore.kernel.org/all/569186c5-8663-43df-a01c-d543f57ce5ca@kernel.org/
> > -- quote: "Fix the analyzer instead."]
>
> We already do this analysis for C++ references, and we might extend it
> to pointers, but with one restriction: an important aspect of
> references that we're relying on is that they're essentially immutable
> pointers, i.e. "T&" semantically behaves like "T* const". This makes
> references behave like SSA values, meaning we can just symbolically
> substitute the initializer. If we can ask the user to mimic references
> by making the local variable "const", we can apply the same logic. (I
> know that using "const" is a bit tricky in C, e.g. "T**" doesn't
> implicitly convert to "const T* const*" as it does in C++. However, I
> would hope that you can always add top-level "const".)
>
> With "non-const" pointers we're unfortunately opening a can of worms:
>
> struct S { int a, b; };
>
> void f(struct S *s)
> {
> int *val = &s->a;
>
> while (...) {
> do_something(val);
> val = &s->b;
> };
> }
>
> The analysis walks the control-flow graph (CFG), and it wants to walk
> it only once. (We handle back edges by checking that the set of
> capabilities matches the set that we previously computed.) Here we
> have val == &s->a when we encounter the call to do_something. But then
> we see a new assignment and a back edge, so our assumption was wrong!
>
> I don't think we want to make multiple rounds over the CFG. We're not
> trying to do symbolic execution. There are performance implications,
> reliability implications, and depending on how far we want to go, we
> might even run into the halting problem.
>
> So if we can restrict this to "T* const", I don't see an issue with
> it, and it should arise naturally from the existing alias analysis for
> references.
>
> Perhaps we can emit notes on -Wthread-safety-precise that suggest
> using "const" pointers or references in case we suspect alias analysis
> has fallen short.
So one of the most important use cases from my perspective is the
interaction with __attribute__((cleanup())), which we use to build
scope based guards in C.
And most of that might be fine with using 'T * const', but the support
for conditional locks explicitly needs to set the pointer NULL when the
acquire fails.
We have a macro used like:
DEFINE_GUARD(mutex, struct mutex *, mutex_lock(_T), mutex_unlock(_T))
Which expands to:
static inline void class_mutex_destructor(struct mutex **p)
{
struct mutex *_T = *p;
if (_T) { mutex_unlock(_T); }
}
static inline struct mutex * class_mutex_constructor(struct mutex *_T)
{
struct mutex * t = ({ mutex_lock(_T); _T; });
return t;
}
This is then typically used like:
scoped_guard (mutex, &my_lock) {
...
}
Which in turn expands to:
for (struct mutex *scope __attribute__((cleanup(class_mutex_destructor))) =
class_mutex_constructor(&my_lock); scope; ({ goto _label; }))
if (0) {
_label:
break;
} else {
...
}
And here I can probably make the guard() thing add const like:
struct mutex * const __UNIQUE_ID_guard_123
__attribute__((cleanup(class_mutex_destructor) =
class_mutex_constructor(&my_lock);
But note that _constructor() is still laundering this through a
non-const version, and while the above could have:
struct mutex * t const = ...
That no longer works for the case of:
DEFINE_GUARD_COND(mutex, _try, mutex_trylock(_T))
which expands to have a constructor like:
static inline struct mutex * class_mutex_try_constructor(struct mutex *_T)
{
struct mutex * t = ({ void *_t = _T; if (_T && !(mutex_trylock(_T))) _t = NULL; _t; });
return t;
}
Notably that NULL assignment on lock failure is not happy with having
that const on.
Note that this combination of trylock and scoped_guard() results in not
taking the for-loop on failure. Eg:
scoped_guard (mutex_trylock, &my_lock) {
print("ponies\n");
}
Will in fact only print 'ponies' when it got the lock.
And yes, all of this is fairly creative use of C, but hey, we get to
have scope based guards, which get rid of a lot of errors in error
paths.
So assuming we can annotate things like so:
static inline void class_mutex_destructor(struct mutex * const *p)
__releases(*p) __no_capability_analysis
{
struct mutex *_T = *p;
if (_T) { mutex_unlock(_T); }
}
static inline struct mutex * class_mutex_constructor(struct mutex *_T)
__acquires(_T) __no_capability_analysis
{
struct mutex * t = ({ mutex_lock(_T); _T; });
return t;
}
static inline struct mutex * class_mutex_try_constructor(struct mutex *_T)
__const_acquires(nonnull, _T) __no_capability_analysis
{
struct mutex * t = ({ void *_t = _T; if (_T && !(mutex_trylock(_T))) _t = NULL; _t; });
return t;
}
the: struct mutex * const in the for() construct might just be enough.
Would this be sufficient consty for it to untangle this web?
next prev parent reply other threads:[~2025-03-06 9:47 UTC|newest]
Thread overview: 29+ messages / expand[flat|nested] mbox.gz Atom feed top
2025-03-05 11:47 Thread Safety Analysis and the Linux kernel Marco Elver
2025-03-05 23:54 ` Puchert, Aaron
2025-03-06 9:47 ` Peter Zijlstra [this message]
2025-03-06 16:18 ` Bart Van Assche
2025-03-07 8:07 ` Peter Zijlstra
2025-03-07 21:50 ` Puchert, Aaron
2025-03-07 21:46 ` Puchert, Aaron
2025-03-06 10:08 ` Peter Zijlstra
2025-03-06 22:18 ` Puchert, Aaron
2025-03-07 7:59 ` Peter Zijlstra
2025-03-07 14:13 ` Peter Zijlstra
2025-03-06 10:37 ` Peter Zijlstra
2025-03-06 23:14 ` Puchert, Aaron
2025-03-07 8:52 ` Peter Zijlstra
2025-03-07 12:52 ` Peter Zijlstra
2025-03-07 14:22 ` Greg Kroah-Hartman
2025-03-07 14:35 ` Peter Zijlstra
2025-03-08 6:06 ` Greg Kroah-Hartman
2025-03-07 23:03 ` Puchert, Aaron
2025-03-06 17:11 ` Paul E. McKenney
2025-03-06 23:24 ` Puchert, Aaron
2025-03-06 23:44 ` Paul E. McKenney
2025-03-07 17:59 ` Puchert, Aaron
2025-03-07 18:24 ` Paul E. McKenney
2025-03-07 12:00 ` Marco Elver
2025-05-05 13:44 ` Marco Elver
2025-06-05 12:44 ` Marco Elver
2025-09-18 10:37 ` Marco Elver
2025-09-18 11:10 ` Peter Zijlstra
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20250306094752.GC16878@noisy.programming.kicks-ass.net \
--to=peterz@infradead.org \
--cc=aaron.puchert@sap.com \
--cc=aaron@aaronballman.com \
--cc=boqun.feng@gmail.com \
--cc=bvanassche@acm.org \
--cc=elver@google.com \
--cc=linux-toolchains@vger.kernel.org \
--cc=llvm@lists.linux.dev \
--cc=paulmck@kernel.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox