From: "Benno Lossin" <lossin@kernel.org>
To: "Lyude Paul" <lyude@redhat.com>, <rust-for-linux@vger.kernel.org>,
<linux-kernel@vger.kernel.org>,
"Thomas Gleixner" <tglx@linutronix.de>
Cc: "Boqun Feng" <boqun.feng@gmail.com>,
"Daniel Almeida" <daniel.almeida@collabora.com>,
"Miguel Ojeda" <ojeda@kernel.org>,
"Alex Gaynor" <alex.gaynor@gmail.com>,
"Gary Guo" <gary@garyguo.net>,
"Björn Roy Baron" <bjorn3_gh@protonmail.com>,
"Andreas Hindborg" <a.hindborg@kernel.org>,
"Alice Ryhl" <aliceryhl@google.com>,
"Trevor Gross" <tmgross@umich.edu>,
"Danilo Krummrich" <dakr@kernel.org>,
"Andrew Morton" <akpm@linux-foundation.org>,
"Peter Zijlstra" <peterz@infradead.org>,
"Ingo Molnar" <mingo@redhat.com>, "Will Deacon" <will@kernel.org>,
"Waiman Long" <longman@redhat.com>
Subject: Re: [PATCH v17 10/16] rust: sync: Introduce lock::Lock::lock_with() and friends
Date: Fri, 23 Jan 2026 23:55:45 +0100 [thread overview]
Message-ID: <DFWC8962IIE1.2KMJBBAP0CC8N@kernel.org> (raw)
In-Reply-To: <20260121223933.1568682-11-lyude@redhat.com>
On Wed Jan 21, 2026 at 11:39 PM CET, Lyude Paul wrote:
> `SpinLockIrq` and `SpinLock` use the exact same underlying C structure,
> with the only real difference being that the former uses the irq_disable()
> and irq_enable() variants for locking/unlocking. These variants can
> introduce some minor overhead in contexts where we already know that
> local processor interrupts are disabled, and as such we want a way to be
> able to skip modifying processor interrupt state in said contexts in order
> to avoid some overhead - just like the current C API allows us to do. So,
> `ContextualBackend` allows us to cast a lock into it's contextless version
> for situations where we already have whatever guarantees would be provided
> by `BackendWithContext::ContextualBackend` in place.
>
> In some hacked-together benchmarks we ran, most of the time this did
> actually seem to lead to a noticeable difference in overhead:
>
> From an aarch64 VM running on a MacBook M4:
> lock() when irq is disabled, 100 times cost Delta { nanos: 500 }
> lock_with() when irq is disabled, 100 times cost Delta { nanos: 292 }
> lock() when irq is enabled, 100 times cost Delta { nanos: 834 }
>
> lock() when irq is disabled, 100 times cost Delta { nanos: 459 }
> lock_with() when irq is disabled, 100 times cost Delta { nanos: 291 }
> lock() when irq is enabled, 100 times cost Delta { nanos: 709 }
>
> From an x86_64 VM (qemu/kvm) running on a i7-13700H
> lock() when irq is disabled, 100 times cost Delta { nanos: 1002 }
> lock_with() when irq is disabled, 100 times cost Delta { nanos: 729 }
> lock() when irq is enabled, 100 times cost Delta { nanos: 1516 }
>
> lock() when irq is disabled, 100 times cost Delta { nanos: 754 }
> lock_with() when irq is disabled, 100 times cost Delta { nanos: 966 }
> lock() when irq is enabled, 100 times cost Delta { nanos: 1227 }
>
> (note that there were some runs on x86_64 where lock() on irq disabled
> vs. lock_with() on irq disabled had equivalent benchmarks, but it very
> much appeared to be a minority of test runs.
>
> While it's not clear how this affects real-world workloads yet, let's add
> this for the time being so we can find out. Implement
> lock::Lock::lock_with() and lock::BackendWithContext::ContextualBackend.
> This makes it so that a `SpinLockIrq` will work like a `SpinLock` if
> interrupts are disabled. So a function:
>
> (&'a SpinLockIrq, &'a InterruptDisabled) -> Guard<'a, .., SpinLockBackend>
>
> makes sense. Note that due to `Guard` and `InterruptDisabled` having the
> same lifetime, interrupts cannot be enabled while the Guard exists.
>
> Signed-off-by: Lyude Paul <lyude@redhat.com>
> Co-developed-by: Boqun Feng <boqun.feng@gmail.com>
> Signed-off-by: Boqun Feng <boqun.feng@gmail.com>
My overall opinion of the design is that we should no longer use the generic
approach with the `Backend` traits. I think I mentioned this on Zulip
already at multiple points. I'm okay with having this extension, but it
would be ideal if we could move to not having a single `Lock` struct,
but one for each locking primitive.
>
> ---
> This was originally two patches, but keeping them split didn't make sense
> after going from BackendInContext to BackendWithContext.
>
> V10:
> * Fix typos - Dirk/Lyude
> * Since we're adding support for context locks to GlobalLock as well, let's
> also make sure to cover try_lock while we're at it and add try_lock_with
> * Add a private function as_lock_in_context() for handling casting from a
> Lock<T, B> to Lock<T, B::ContextualBackend> so we don't have to duplicate
> safety comments
> V11:
> * Fix clippy::ref_as_ptr error in Lock::as_lock_in_context()
> V14:
> * Add benchmark results, rewrite commit message
> V17:
> * Introduce `BackendWithContext`, move context-related bits into there and
> out of `Backend`.
> * Add missing #[must_use = …] for try_lock_with()
> * Remove all unsafe code from lock_with() and try_lock_with():
> Somehow I never noticed that literally none of the unsafe code in these
> two functions is needed with as_lock_in_context()...
>
> rust/kernel/sync/lock.rs | 71 ++++++++++++++++++++++++++++++-
> rust/kernel/sync/lock/spinlock.rs | 48 ++++++++++++++++++++-
> 2 files changed, 117 insertions(+), 2 deletions(-)
>
> diff --git a/rust/kernel/sync/lock.rs b/rust/kernel/sync/lock.rs
> index 46a57d1fc309d..9f6d7b381bd15 100644
> --- a/rust/kernel/sync/lock.rs
> +++ b/rust/kernel/sync/lock.rs
> @@ -30,10 +30,15 @@
> /// is owned, that is, between calls to [`lock`] and [`unlock`].
> /// - Implementers must also ensure that [`relock`] uses the same locking method as the original
> /// lock operation.
> +/// - Implementers must ensure if [`BackendInContext`] is a [`Backend`], it's safe to acquire the
> +/// lock under the [`Context`], the [`State`] of two backends must be the same.
This isn't needed, since we don't have `Backend::Context` any longer.
> ///
> /// [`lock`]: Backend::lock
> /// [`unlock`]: Backend::unlock
> /// [`relock`]: Backend::relock
> +/// [`BackendInContext`]: Backend::BackendInContext
> +/// [`Context`]: Backend::Context
> +/// [`State`]: Backend::State
Same for these.
> pub unsafe trait Backend {
> /// The state required by the lock.
> type State;
> @@ -97,6 +102,34 @@ unsafe fn relock(ptr: *mut Self::State, guard_state: &mut Self::GuardState) {
> unsafe fn assert_is_held(ptr: *mut Self::State);
> }
>
> +/// A lock [`Backend`] with a [`ContextualBackend`] that can make lock acquisition cheaper.
> +///
> +/// Some locks, such as [`SpinLockIrq`](super::SpinLockIrq), can only be acquired in specific
> +/// hardware contexts (e.g. local processor interrupts disabled). Entering and exiting these
> +/// contexts incurs additional overhead. But this overhead may be avoided if we know ahead of time
> +/// that we are already within the correct context for a given lock as we can then skip any costly
> +/// operations required for entering/exiting said context.
> +///
> +/// Any lock implementing this trait requires such a interrupt context, and can provide cheaper
> +/// lock-acquisition functions through [`Lock::lock_with`] and [`Lock::try_lock_with`] as long as a
> +/// context token of type [`Context`] is available.
> +///
> +/// # Safety
> +///
> +/// - Implementors must ensure that it is safe to acquire the lock under [`Context`].
This safety comment needs some improvements. We probably should just put
the entire cast into this.
> +///
> +/// [`ContextualBackend`]: BackendWithContext::ContextualBackend
> +/// [`Context`]: BackendWithContext::Context
> +pub unsafe trait BackendWithContext: Backend {
> + /// The context which must be provided in order to acquire the lock with the
> + /// [`ContextualBackend`](BackendWithContext::ContextualBackend).
> + type Context<'a>;
> +
> + /// The alternative cheaper backend we can use if a [`Context`](BackendWithContext::Context) is
> + /// provided.
> + type ContextualBackend: Backend<State = Self::State>;
> +}
> +
> /// A mutual exclusion primitive.
> ///
> /// Exposes one of the kernel locking primitives. Which one is exposed depends on the lock
> @@ -169,7 +202,8 @@ pub unsafe fn from_raw<'a>(ptr: *mut B::State) -> &'a Self {
>
> impl<T: ?Sized, B: Backend> Lock<T, B> {
> /// Acquires the lock and gives the caller access to the data protected by it.
> - pub fn lock(&self) -> Guard<'_, T, B> {
> + #[inline]
> + pub fn lock<'a>(&'a self) -> Guard<'a, T, B> {
Why this change?
> // SAFETY: The constructor of the type calls `init`, so the existence of the object proves
> // that `init` was called.
> let state = unsafe { B::lock(self.state.get()) };
> @@ -189,6 +223,41 @@ pub fn try_lock(&self) -> Option<Guard<'_, T, B>> {
> }
> }
>
> +impl<T: ?Sized, B: BackendWithContext> Lock<T, B> {
> + /// Casts the lock as a `Lock<T, B::ContextualBackend>`.
> + fn as_lock_in_context<'a>(
> + &'a self,
> + _context: B::Context<'a>,
> + ) -> &'a Lock<T, B::ContextualBackend>
> + where
> + B::ContextualBackend: Backend,
> + {
> + // SAFETY:
> + // - Per the safety guarantee of `Backend`, if `B::ContextualBackend` and `B` should
> + // have the same state, the layout of the lock is the same so it's safe to convert one to
> + // another.
This also relies on `Lock` being `repr(C)`. `repr(Rust)` types are
allowed to change layout in cases where their generics are substituted
for others (even if those other ones have the same layout!).
Cheers,
Benno
next prev parent reply other threads:[~2026-01-23 22:55 UTC|newest]
Thread overview: 47+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-01-21 22:39 [PATCH v17 00/16] Refcounted interrupts, SpinLockIrq for rust Lyude Paul
2026-01-21 22:39 ` [PATCH v17 01/16] preempt: Introduce HARDIRQ_DISABLE_BITS Lyude Paul
2026-01-21 22:39 ` [PATCH v17 02/16] preempt: Track NMI nesting to separate per-CPU counter Lyude Paul
2026-02-03 11:44 ` Peter Zijlstra
2026-02-06 1:22 ` Joel Fernandes
2026-02-03 12:15 ` Peter Zijlstra
2026-02-04 11:12 ` Peter Zijlstra
2026-02-04 12:32 ` Gary Guo
2026-02-04 13:00 ` Peter Zijlstra
2026-02-05 21:40 ` Boqun Feng
2026-02-05 22:17 ` Joel Fernandes
2026-02-06 0:50 ` Joel Fernandes
2026-02-06 1:14 ` Boqun Feng
2026-02-06 1:24 ` Joel Fernandes
2026-02-06 2:51 ` Boqun Feng
2026-02-06 8:13 ` Joel Fernandes
2026-02-06 15:28 ` Boqun Feng
2026-02-06 16:00 ` Joel Fernandes
2026-02-06 16:16 ` Boqun Feng
2026-02-07 22:11 ` Joel Fernandes
2026-02-06 8:42 ` Peter Zijlstra
2026-02-05 22:07 ` Boqun Feng
2026-02-06 8:45 ` Peter Zijlstra
2026-01-21 22:39 ` [PATCH v17 03/16] preempt: Introduce __preempt_count_{sub, add}_return() Lyude Paul
2026-01-21 22:39 ` [PATCH v17 04/16] openrisc: Include <linux/cpumask.h> in smp.h Lyude Paul
2026-01-21 22:39 ` [PATCH v17 05/16] irq & spin_lock: Add counted interrupt disabling/enabling Lyude Paul
2026-01-21 22:39 ` [PATCH v17 06/16] irq: Add KUnit test for refcounted interrupt enable/disable Lyude Paul
2026-01-30 7:43 ` David Gow
2026-01-21 22:39 ` [PATCH v17 07/16] rust: Introduce interrupt module Lyude Paul
2026-01-21 22:39 ` [PATCH v17 08/16] rust: helper: Add spin_{un,}lock_irq_{enable,disable}() helpers Lyude Paul
2026-01-26 13:25 ` Gary Guo
2026-01-21 22:39 ` [PATCH v17 09/16] rust: sync: Add SpinLockIrq Lyude Paul
2026-01-23 22:26 ` Benno Lossin
2026-01-21 22:39 ` [PATCH v17 10/16] rust: sync: Introduce lock::Lock::lock_with() and friends Lyude Paul
2026-01-22 11:56 ` kernel test robot
2026-01-23 22:55 ` Benno Lossin [this message]
2026-01-26 13:31 ` Gary Guo
2026-01-21 22:39 ` [PATCH v17 11/16] rust: sync: Expose lock::Backend Lyude Paul
2026-01-23 22:56 ` Benno Lossin
2026-01-21 22:39 ` [PATCH v17 12/16] rust: sync: lock/global: Rename B to G in trait bounds Lyude Paul
2026-01-21 22:39 ` [PATCH v17 13/16] rust: sync: Add a lifetime parameter to lock::global::GlobalGuard Lyude Paul
2026-01-21 22:39 ` [PATCH v17 14/16] rust: sync: lock/global: Add Backend parameter to GlobalGuard Lyude Paul
2026-01-21 22:39 ` [PATCH v17 15/16] rust: sync: lock/global: Add ContextualBackend support to GlobalLock Lyude Paul
2026-01-21 22:39 ` [PATCH v17 16/16] locking: Switch to _irq_{disable,enable}() variants in cleanup guards Lyude Paul
2026-01-26 13:24 ` [PATCH v17 00/16] Refcounted interrupts, SpinLockIrq for rust Gary Guo
2026-01-26 16:17 ` Boqun Feng
2026-02-03 0:36 ` Boqun Feng
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=DFWC8962IIE1.2KMJBBAP0CC8N@kernel.org \
--to=lossin@kernel.org \
--cc=a.hindborg@kernel.org \
--cc=akpm@linux-foundation.org \
--cc=alex.gaynor@gmail.com \
--cc=aliceryhl@google.com \
--cc=bjorn3_gh@protonmail.com \
--cc=boqun.feng@gmail.com \
--cc=dakr@kernel.org \
--cc=daniel.almeida@collabora.com \
--cc=gary@garyguo.net \
--cc=linux-kernel@vger.kernel.org \
--cc=longman@redhat.com \
--cc=lyude@redhat.com \
--cc=mingo@redhat.com \
--cc=ojeda@kernel.org \
--cc=peterz@infradead.org \
--cc=rust-for-linux@vger.kernel.org \
--cc=tglx@linutronix.de \
--cc=tmgross@umich.edu \
--cc=will@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