From: Gary Guo <gary@garyguo.net>
To: Asahi Lina <lina@asahilina.net>
Cc: "Miguel Ojeda" <ojeda@kernel.org>,
"Alex Gaynor" <alex.gaynor@gmail.com>,
"Wedson Almeida Filho" <wedsonaf@gmail.com>,
"Boqun Feng" <boqun.feng@gmail.com>,
"Björn Roy Baron" <bjorn3_gh@protonmail.com>,
"Benno Lossin" <benno.lossin@proton.me>,
"Masahiro Yamada" <masahiroy@kernel.org>,
"Nathan Chancellor" <nathan@kernel.org>,
"Nick Desaulniers" <ndesaulniers@google.com>,
"Nicolas Schier" <nicolas@fjasle.eu>, "Tom Rix" <trix@redhat.com>,
"Daniel Vetter" <daniel@ffwll.ch>,
"Hector Martin" <marcan@marcan.st>,
"Sven Peter" <sven@svenpeter.dev>,
"Alyssa Rosenzweig" <alyssa@rosenzweig.io>,
asahi@lists.linux.dev, rust-for-linux@vger.kernel.org,
linux-kernel@vger.kernel.org, linux-kbuild@vger.kernel.org,
llvm@lists.linux.dev
Subject: Re: [PATCH RFC 07/11] rust: sync: Implement dynamic lockdep class creation
Date: Sat, 15 Jul 2023 16:47:47 +0100 [thread overview]
Message-ID: <20230715164747.7ba23b36.gary@garyguo.net> (raw)
In-Reply-To: <20230714-classless_lockdep-v1-7-229b9671ce31@asahilina.net>
On Fri, 14 Jul 2023 18:13:59 +0900
Asahi Lina <lina@asahilina.net> wrote:
> Using macros to create lock classes all over the place is unergonomic,
> and makes it impossible to add new features that require lock classes to
> code such as Arc<> without changing all callers.
>
> Rust has the ability to track the caller's identity by file/line/column
> number, and we can use that to dynamically generate lock classes
> instead.
>
> Signed-off-by: Asahi Lina <lina@asahilina.net>
> ---
> rust/kernel/sync/lockdep.rs | 147 ++++++++++++++++++++++++++++++++++++++++-
> rust/kernel/sync/no_lockdep.rs | 8 +++
> 2 files changed, 154 insertions(+), 1 deletion(-)
>
> diff --git a/rust/kernel/sync/lockdep.rs b/rust/kernel/sync/lockdep.rs
> index d8328f4275fb..fbf9f6ed403d 100644
> --- a/rust/kernel/sync/lockdep.rs
> +++ b/rust/kernel/sync/lockdep.rs
> @@ -5,7 +5,19 @@
> //! This module abstracts the parts of the kernel lockdep API relevant to Rust
> //! modules, including lock classes.
>
> -use crate::types::Opaque;
> +use crate::{
> + c_str, fmt,
> + init::InPlaceInit,
> + new_mutex,
> + prelude::{Box, Result, Vec},
> + str::{CStr, CString},
> + sync::Mutex,
> + types::Opaque,
> +};
> +
> +use core::hash::{Hash, Hasher};
> +use core::pin::Pin;
> +use core::sync::atomic::{AtomicPtr, Ordering};
>
> /// Represents a lockdep class. It's a wrapper around C's `lock_class_key`.
> #[repr(transparent)]
> @@ -42,3 +54,136 @@ pub(crate) fn as_ptr(&self) -> *mut bindings::lock_class_key {
> // actually dereferenced.
> unsafe impl Send for LockClassKey {}
> unsafe impl Sync for LockClassKey {}
> +
> +// Location is 'static but not really, since module unloads will
> +// invalidate existing static Locations within that module.
> +// To avoid breakage, we maintain our own location struct which is
> +// dynamically allocated on first reference. We store a hash of the
> +// whole location (including the filename string), as well as the
> +// line and column separately. The assumption is that this whole
> +// struct is highly unlikely to ever collide with a reasonable
> +// hash (this saves us from having to check the filename string
> +// itself).
> +#[derive(PartialEq, Debug)]
> +struct LocationKey {
> + hash: u64,
> + line: u32,
> + column: u32,
> +}
> +
> +struct DynLockClassKey {
> + key: Opaque<bindings::lock_class_key>,
> + loc: LocationKey,
> + name: CString,
> +}
> +
> +impl LocationKey {
> + fn new(loc: &'static core::panic::Location<'static>) -> Self {
> + let mut hasher = crate::siphash::SipHasher::new();
> + loc.hash(&mut hasher);
> +
> + LocationKey {
> + hash: hasher.finish(),
> + line: loc.line(),
> + column: loc.column(),
> + }
> + }
> +}
> +
> +impl DynLockClassKey {
> + fn key(&'static self) -> LockClassKey {
> + LockClassKey(self.key.get())
> + }
I don't understand why PATCH 06 is needed. If we keep the current
`LockClassKey` definition this could just be returning `'static
LockClassKey`, which is a simple `&self.key`.
> +
> + fn name(&'static self) -> &CStr {
> + &self.name
> + }
> +}
> +
> +const LOCK_CLASS_BUCKETS: usize = 1024;
> +
> +#[track_caller]
> +fn caller_lock_class_inner() -> Result<&'static DynLockClassKey> {
> + // This is just a hack to make the below static array initialization work.
> + #[allow(clippy::declare_interior_mutable_const)]
> + const ATOMIC_PTR: AtomicPtr<Mutex<Vec<&'static DynLockClassKey>>> =
> + AtomicPtr::new(core::ptr::null_mut());
> +
> + #[allow(clippy::complexity)]
> + static LOCK_CLASSES: [AtomicPtr<Mutex<Vec<&'static DynLockClassKey>>>; LOCK_CLASS_BUCKETS] =
> + [ATOMIC_PTR; LOCK_CLASS_BUCKETS];
> +
> + let loc = core::panic::Location::caller();
> + let loc_key = LocationKey::new(loc);
> +
> + let index = (loc_key.hash % (LOCK_CLASS_BUCKETS as u64)) as usize;
> + let slot = &LOCK_CLASSES[index];
> +
> + let mut ptr = slot.load(Ordering::Relaxed);
> + if ptr.is_null() {
> + let new_element = Box::pin_init(new_mutex!(Vec::new()))?;
> +
> + if let Err(e) = slot.compare_exchange(
> + core::ptr::null_mut(),
> + // SAFETY: We never move out of this Box
> + Box::into_raw(unsafe { Pin::into_inner_unchecked(new_element) }),
> + Ordering::Relaxed,
> + Ordering::Relaxed,
> + ) {
> + // SAFETY: We just got this pointer from `into_raw()`
> + unsafe { Box::from_raw(e) };
> + }
> +
> + ptr = slot.load(Ordering::Relaxed);
> + assert!(!ptr.is_null());
> + }
> +
> + // SAFETY: This mutex was either just created above or previously allocated,
> + // and we never free these objects so the pointer is guaranteed to be valid.
> + let mut guard = unsafe { (*ptr).lock() };
> +
> + for i in guard.iter() {
> + if i.loc == loc_key {
> + return Ok(i);
> + }
> + }
> +
> + // We immediately leak the class, so it becomes 'static
> + let new_class = Box::leak(Box::try_new(DynLockClassKey {
> + key: Opaque::zeroed(),
> + loc: loc_key,
> + name: CString::try_from_fmt(fmt!("{}:{}:{}", loc.file(), loc.line(), loc.column()))?,
> + })?);
> +
> + // SAFETY: This is safe to call with a pointer to a dynamically allocated lockdep key,
> + // and we never free the objects so it is safe to never unregister the key.
> + unsafe { bindings::lockdep_register_key(new_class.key.get()) };
> +
> + guard.try_push(new_class)?;
> +
> + Ok(new_class)
> +}
> +
> +#[track_caller]
> +pub(crate) fn caller_lock_class() -> (LockClassKey, &'static CStr) {
> + match caller_lock_class_inner() {
> + Ok(a) => (a.key(), a.name()),
> + Err(_) => {
> + crate::pr_err!(
> + "Failed to dynamically allocate lock class, lockdep may be unreliable.\n"
> + );
> +
> + let loc = core::panic::Location::caller();
> + // SAFETY: LockClassKey is opaque and the lockdep implementation only needs
> + // unique addresses for statically allocated keys, so it is safe to just cast
> + // the Location reference directly into a LockClassKey. However, this will
> + // result in multiple keys for the same callsite due to monomorphization,
> + // as well as spuriously destroyed keys when the static key is allocated in
> + // the wrong module, which is what makes this unreliable.
If the only purpose of introducing `StaticLockClassKey` and change
`LockClassKey` is to make this fallback path work, then I don't think
that change is worth it. I don't really see an issue with forging a
`'static LockClassKey' from a `'static Location`, especially since you
can't really do any memory access with `LockClassKey`.
> + (
> + LockClassKey(loc as *const _ as *mut _),
> + c_str!("fallback_lock_class"),
> + )
> + }
> + }
> +}
> diff --git a/rust/kernel/sync/no_lockdep.rs b/rust/kernel/sync/no_lockdep.rs
> index 518ec0bf9a7d..de53c4de7fbe 100644
> --- a/rust/kernel/sync/no_lockdep.rs
> +++ b/rust/kernel/sync/no_lockdep.rs
> @@ -4,6 +4,8 @@
> //!
> //! Takes the place of the `lockdep` module when lockdep is disabled.
>
> +use crate::{c_str, str::CStr};
> +
> /// A dummy, zero-sized lock class.
> pub struct StaticLockClassKey();
>
> @@ -28,3 +30,9 @@ pub(crate) fn as_ptr(&self) -> *mut bindings::lock_class_key {
> core::ptr::null_mut()
> }
> }
> +
> +pub(crate) fn caller_lock_class() -> (LockClassKey, &'static CStr) {
> + static DUMMY_LOCK_CLASS: StaticLockClassKey = StaticLockClassKey::new();
> +
> + (DUMMY_LOCK_CLASS.key(), c_str!("dummy"))
> +}
>
next prev parent reply other threads:[~2023-07-15 15:47 UTC|newest]
Thread overview: 31+ messages / expand[flat|nested] mbox.gz Atom feed top
2023-07-14 9:13 [PATCH RFC 00/11] rust: Implicit lock class creation & Arc Lockdep integration Asahi Lina
2023-07-14 9:13 ` [PATCH RFC 01/11] rust: types: Add Opaque::zeroed() Asahi Lina
2023-07-14 10:15 ` Alice Ryhl
2023-07-15 14:27 ` Gary Guo
2023-07-14 9:13 ` [PATCH RFC 02/11] rust: lock: Add Lock::pin_init() Asahi Lina
2023-07-15 14:29 ` Gary Guo
2023-07-14 9:13 ` [PATCH RFC 03/11] rust: Use absolute paths to build Rust objects Asahi Lina
2023-07-15 14:35 ` Gary Guo
2023-07-16 7:53 ` Asahi Lina
2023-07-14 9:13 ` [PATCH RFC 04/11] rust: siphash: Add a simple siphash abstraction Asahi Lina
2023-07-14 14:28 ` Martin Rodriguez Reboredo
2023-07-15 14:52 ` Gary Guo
2023-07-14 9:13 ` [PATCH RFC 05/11] rust: sync: Add dummy LockClassKey implementation for !CONFIG_LOCKDEP Asahi Lina
2023-07-14 14:57 ` Martin Rodriguez Reboredo
2023-07-14 9:13 ` [PATCH RFC 06/11] rust: sync: Replace static LockClassKey refs with a pointer wrapper Asahi Lina
2023-07-14 15:10 ` Martin Rodriguez Reboredo
2023-07-14 9:13 ` [PATCH RFC 07/11] rust: sync: Implement dynamic lockdep class creation Asahi Lina
2023-07-14 19:56 ` Martin Rodriguez Reboredo
2023-07-15 15:47 ` Gary Guo [this message]
2023-07-14 9:14 ` [PATCH RFC 08/11] rust: sync: Classless Lock::new() and pin_init() Asahi Lina
2023-07-14 9:14 ` [PATCH RFC 09/11] rust: init: Update documentation for new mutex init style Asahi Lina
2023-07-14 9:14 ` [PATCH RFC 10/11] rust: sync: Add LockdepMap abstraction Asahi Lina
2023-07-14 9:14 ` [PATCH RFC 11/11] rust: sync: arc: Add lockdep integration Asahi Lina
2023-07-15 16:00 ` Gary Guo
2023-07-14 10:13 ` [PATCH RFC 00/11] rust: Implicit lock class creation & Arc Lockdep integration Alice Ryhl
2023-07-14 12:20 ` Asahi Lina
2023-07-14 13:59 ` Alice Ryhl
2023-07-14 15:21 ` Boqun Feng
2023-07-16 6:56 ` Asahi Lina
2023-07-15 14:25 ` Gary Guo
2023-07-18 16:48 ` 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=20230715164747.7ba23b36.gary@garyguo.net \
--to=gary@garyguo.net \
--cc=alex.gaynor@gmail.com \
--cc=alyssa@rosenzweig.io \
--cc=asahi@lists.linux.dev \
--cc=benno.lossin@proton.me \
--cc=bjorn3_gh@protonmail.com \
--cc=boqun.feng@gmail.com \
--cc=daniel@ffwll.ch \
--cc=lina@asahilina.net \
--cc=linux-kbuild@vger.kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=llvm@lists.linux.dev \
--cc=marcan@marcan.st \
--cc=masahiroy@kernel.org \
--cc=nathan@kernel.org \
--cc=ndesaulniers@google.com \
--cc=nicolas@fjasle.eu \
--cc=ojeda@kernel.org \
--cc=rust-for-linux@vger.kernel.org \
--cc=sven@svenpeter.dev \
--cc=trix@redhat.com \
--cc=wedsonaf@gmail.com \
/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;
as well as URLs for NNTP newsgroup(s).