Message ID | 20230714-classless_lockdep-v1-0-229b9671ce31@asahilina.net |
---|---|
Headers |
Return-Path: <linux-kernel-owner@vger.kernel.org> Delivered-To: ouuuleilei@gmail.com Received: by 2002:a59:a6b2:0:b0:3e4:2afc:c1 with SMTP id c18csp2387075vqm; Fri, 14 Jul 2023 02:52:20 -0700 (PDT) X-Google-Smtp-Source: APBJJlFcSzNU8egbch6nlVKi2/UxeWEa42MYMp8hgtAcr1Nf5P6aqaTkr8tbS718Iaoiatl63bQU X-Received: by 2002:a05:6a20:734e:b0:12d:e976:5064 with SMTP id v14-20020a056a20734e00b0012de9765064mr3557651pzc.29.1689328340394; Fri, 14 Jul 2023 02:52:20 -0700 (PDT) ARC-Seal: i=1; a=rsa-sha256; t=1689328340; cv=none; d=google.com; s=arc-20160816; b=ZWwBIGyxu1l3n2tKUXRM15MgfXsISKZWGFvm31VWPrJw/kN1vtYkIY/od3VwpePadc ivN2Dv4vg/9CrfLQhHEF4At2/Wlq2wsqloE3Gk8VRcGyDWR3dSBMwvxL7GrZlKnCIZIX 4CA1N6PJHAN/x75qbJUnrUfGb4lRXPlJ7zn9mI3d5R2JgNfsD75Zcp+ak/He3Giu1p7I tBesskoIJF3+iGDzsMLijvxAXOZmyZvNydC05KNdL485Jqt7bfFyLl9OtRiNiyeRCheP 9/GhhjNwmDdcDvzVmhwi4OjAkPrA6buTNuT+F5NsN6/0i8XoyMYa6gwMVWeD9L7MYwWk 8a+A== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20160816; h=list-id:precedence:cc:to:content-transfer-encoding:mime-version :message-id:date:subject:from:dkim-signature; bh=VgFOL/DOZ0RFLoZeig2O8qe+zSDKuIFhr99IoBL3Gr8=; fh=P69duW5QZmRj9b1Pp5XKOSqIgFMUDbjoyFZWqOwQYzw=; b=HCDZ3oYvusfX9IuF0yBEKZCyuFgfj2TBRjke5uOphFR1IHQE+mdxTMJrhQe4+arCq1 StC4LPzDhurlKlcpW+PyuexJHYr/U3keicyUXNIFoUnwiqhSVgOne5g2nmCgCQRBKDOh UmOxy+/m/8Sw2FimctB9TWoPdySdf+vbQAhGLx6YMr2EexpXCDbxC60sBE1lBdsm5r6V mxPCjzTu9S5ObYv/5YY8TnanOazJiXj1GMa+qbawjfD4c2bYt7hM3J/wCwVBSBZGWisc qp0MroMTjAaNWTacU3fmMqWRlsvnX/l+2+gy4Duq2xWga/gynK9NDABec30J+TICS1Ne VxHA== ARC-Authentication-Results: i=1; mx.google.com; dkim=pass header.i=@asahilina.net header.s=default header.b=DWOuvqjn; spf=pass (google.com: domain of linux-kernel-owner@vger.kernel.org designates 2620:137:e000::1:20 as permitted sender) smtp.mailfrom=linux-kernel-owner@vger.kernel.org; dmarc=pass (p=QUARANTINE sp=QUARANTINE dis=NONE) header.from=asahilina.net Received: from out1.vger.email (out1.vger.email. [2620:137:e000::1:20]) by mx.google.com with ESMTP id n13-20020a170902f60d00b001b9e82a6beesi922529plg.548.2023.07.14.02.52.08; Fri, 14 Jul 2023 02:52:20 -0700 (PDT) Received-SPF: pass (google.com: domain of linux-kernel-owner@vger.kernel.org designates 2620:137:e000::1:20 as permitted sender) client-ip=2620:137:e000::1:20; Authentication-Results: mx.google.com; dkim=pass header.i=@asahilina.net header.s=default header.b=DWOuvqjn; spf=pass (google.com: domain of linux-kernel-owner@vger.kernel.org designates 2620:137:e000::1:20 as permitted sender) smtp.mailfrom=linux-kernel-owner@vger.kernel.org; dmarc=pass (p=QUARANTINE sp=QUARANTINE dis=NONE) header.from=asahilina.net Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S235480AbjGNJbv (ORCPT <rfc822;tebrre53rla2o@gmail.com> + 99 others); Fri, 14 Jul 2023 05:31:51 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:55864 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S234417AbjGNJbn (ORCPT <rfc822;linux-kernel@vger.kernel.org>); Fri, 14 Jul 2023 05:31:43 -0400 Received: from mail.marcansoft.com (marcansoft.com [212.63.210.85]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id A5A793599; Fri, 14 Jul 2023 02:31:14 -0700 (PDT) Received: from [127.0.0.1] (localhost [127.0.0.1]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (4096 bits) server-digest SHA256) (No client certificate requested) (Authenticated sender: linasend@asahilina.net) by mail.marcansoft.com (Postfix) with ESMTPSA id 2DD105BC3A; Fri, 14 Jul 2023 09:14:00 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=asahilina.net; s=default; t=1689326046; bh=NNlQg01GoROuwHT7V4Ok4QbA2Wacecl8a9cR39wPQX4=; h=From:Subject:Date:To:Cc; b=DWOuvqjnL+WVRlQCchmZp/1Q5QSmwk1TXPN8elD+Lr+TEOyfN68h44fdUmUcdHUN2 7A4F4XNCMes02IwEzHvDFlsJpVL0C1B4V1z1Ue0okL8UxvAiq4YGiBco3taDmSFjt/ jS1+M7N5xtG4lbjc5aUXgomR7zvuNjImD/mfiMO2d2SBUVIwbYtnjKb3nbQzJMX+ol vHAaNDJRwALfpj1vg6IIcWkXw5ZDFpjpkKMYGKkPHoYaEQR6LoFiC9FK29dna86ls9 2qOlhTZmdmXLAN5YMfQKDpOaFiZjQ2tR+HS58wyprIbaP33CVyONvqagYL1nunBGyZ /QtxTuAPRiOWQ== From: Asahi Lina <lina@asahilina.net> Subject: [PATCH RFC 00/11] rust: Implicit lock class creation & Arc Lockdep integration Date: Fri, 14 Jul 2023 18:13:52 +0900 Message-Id: <20230714-classless_lockdep-v1-0-229b9671ce31@asahilina.net> MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: 7bit X-B4-Tracking: v=1; b=H4sIANARsWQC/6tWKk4tykwtVrJSqFYqSi3LLM7MzwNyDHUUlJIzE vPSU3UzU4B8JSMDI2MDc0MT3eScxOLinNTi4vic/OTslNQC3TTDFFNLc6O0JJOkRCWgvoKi1LT MCrCZ0UpBbs5KsbW1AJoiJw1oAAAA To: Miguel Ojeda <ojeda@kernel.org>, Alex Gaynor <alex.gaynor@gmail.com>, Wedson Almeida Filho <wedsonaf@gmail.com>, Boqun Feng <boqun.feng@gmail.com>, Gary Guo <gary@garyguo.net>, =?utf-8?q?Bj=C3=B6rn_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> Cc: 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, Asahi Lina <lina@asahilina.net> X-Mailer: b4 0.12.3 X-Developer-Signature: v=1; a=ed25519-sha256; t=1689326040; l=3990; i=lina@asahilina.net; s=20230221; h=from:subject:message-id; bh=NNlQg01GoROuwHT7V4Ok4QbA2Wacecl8a9cR39wPQX4=; b=/iyr7ancBEF0XHvo1tC+PR5V7K/FO06oJ0Cy/w4WDvBHh+I0Op8T6d5uYejSOZ+TUqNdVn/Ns klBa8CShSlIAxB40VpRMh8VI93PqdHiAsoOvp8NK/p+ZA/7SUcFENqu X-Developer-Key: i=lina@asahilina.net; a=ed25519; pk=Qn8jZuOtR1m5GaiDfTrAoQ4NE1XoYVZ/wmt5YtXWFC4= X-Spam-Status: No, score=-2.1 required=5.0 tests=BAYES_00,DKIM_SIGNED, DKIM_VALID,DKIM_VALID_AU,DKIM_VALID_EF,RCVD_IN_DNSWL_BLOCKED, SPF_HELO_NONE,SPF_PASS,T_SCC_BODY_TEXT_LINE,URIBL_BLOCKED autolearn=ham autolearn_force=no version=3.4.6 X-Spam-Checker-Version: SpamAssassin 3.4.6 (2021-04-09) on lindbergh.monkeyblade.net Precedence: bulk List-ID: <linux-kernel.vger.kernel.org> X-Mailing-List: linux-kernel@vger.kernel.org X-getmail-retrieved-from-mailbox: INBOX X-GMAIL-THRID: 1771389153563876110 X-GMAIL-MSGID: 1771389153563876110 |
Series |
rust: Implicit lock class creation & Arc Lockdep integration
|
|
Message
Asahi Lina
July 14, 2023, 9:13 a.m. UTC
Begone, lock classes!
As discussed in meetings/etc, we would really like to support implicit
lock class creation for Rust code. Right now, lock classes are created
using macros and passed around (similar to C). Unfortunately, Rust
macros don't look like Rust functions, which means adding lockdep to a
type is a breaking API change. This makes Rust mutex creation rather
ugly, with the new_mutex!() macro and friends.
Implicit lock classes have to be unique per instantiation code site.
Notably, with Rust generics and monomorphization, this is not the same
as unique per generated code instance. If this weren't the case, we
could use inline functions and asm!() magic to try to create lock
classes that have the right uniqueness semantics. But that doesn't work,
since it would create too many lock classes for the same actual lock
creation in the source code.
But Rust does have one trick we can use: it can track the caller
location (as file:line:column), across multiple functions. This works
using an implicit argument that gets passed around, which is exactly the
thing we do for lock classes. The tricky bit is that, while the value of
these Location objects has the semantics we want (unique value per
source code location), there is no guarantee that they are deduplicated
in memory.
So we use a hash table, and map Location values to lock classes. Et
voila, implicit lock class support!
This lets us clean up the Mutex & co APIs and make them look a lot more
Rust-like, but it also means we can now throw Lockdep into more APIs
without breaking the API. And so we can pull a neat trick: adding
Lockdep support into Arc<T>. This catches cases where the Arc Drop
implementation could create a locking correctness violation only when
the reference count drops to 0 at that particular drop site, which is
otherwise not detectable unless that condition actually happens at
runtime. Since Drop is "magic" in Rust and Drop codepaths very difficult
to audit, this helps a lot.
For the initial RFC, this implements the new API only for Mutex. If this
looks good, I can extend it to CondVar & friends in the next version.
This series also folds in a few related minor dependencies / changes
(like the pin_init mutex stuff).
Signed-off-by: Asahi Lina <lina@asahilina.net>
---
Asahi Lina (11):
rust: types: Add Opaque::zeroed()
rust: lock: Add Lock::pin_init()
rust: Use absolute paths to build Rust objects
rust: siphash: Add a simple siphash abstraction
rust: sync: Add dummy LockClassKey implementation for !CONFIG_LOCKDEP
rust: sync: Replace static LockClassKey refs with a pointer wrapper
rust: sync: Implement dynamic lockdep class creation
rust: sync: Classless Lock::new() and pin_init()
rust: init: Update documentation for new mutex init style
rust: sync: Add LockdepMap abstraction
rust: sync: arc: Add lockdep integration
lib/Kconfig.debug | 8 ++
rust/Makefile | 2 +-
rust/bindings/bindings_helper.h | 2 +
rust/helpers.c | 24 ++++
rust/kernel/init.rs | 22 ++--
rust/kernel/lib.rs | 1 +
rust/kernel/siphash.rs | 39 +++++++
rust/kernel/sync.rs | 33 ++----
rust/kernel/sync/arc.rs | 71 +++++++++++-
rust/kernel/sync/condvar.rs | 2 +-
rust/kernel/sync/lock.rs | 68 ++++++++++-
rust/kernel/sync/lock/mutex.rs | 15 ++-
rust/kernel/sync/lock/spinlock.rs | 2 +-
rust/kernel/sync/lockdep.rs | 229 ++++++++++++++++++++++++++++++++++++++
rust/kernel/sync/no_lockdep.rs | 38 +++++++
rust/kernel/types.rs | 7 +-
scripts/Makefile.build | 8 +-
17 files changed, 525 insertions(+), 46 deletions(-)
---
base-commit: 7eb28ae62e16abc207c90064ad2b824c19566fe2
change-id: 20230714-classless_lockdep-f1d5972fb4ba
Thank you,
~~ Lina
Comments
Asahi Lina <lina@asahilina.net> writes: > Begone, lock classes! > > As discussed in meetings/etc, we would really like to support implicit > lock class creation for Rust code. Right now, lock classes are created > using macros and passed around (similar to C). Unfortunately, Rust > macros don't look like Rust functions, which means adding lockdep to a > type is a breaking API change. This makes Rust mutex creation rather > ugly, with the new_mutex!() macro and friends. > > Implicit lock classes have to be unique per instantiation code site. > Notably, with Rust generics and monomorphization, this is not the same > as unique per generated code instance. If this weren't the case, we > could use inline functions and asm!() magic to try to create lock > classes that have the right uniqueness semantics. But that doesn't work, > since it would create too many lock classes for the same actual lock > creation in the source code. > > But Rust does have one trick we can use: it can track the caller > location (as file:line:column), across multiple functions. This works > using an implicit argument that gets passed around, which is exactly the > thing we do for lock classes. The tricky bit is that, while the value of > these Location objects has the semantics we want (unique value per > source code location), there is no guarantee that they are deduplicated > in memory. > > So we use a hash table, and map Location values to lock classes. Et > voila, implicit lock class support! > > This lets us clean up the Mutex & co APIs and make them look a lot more > Rust-like, but it also means we can now throw Lockdep into more APIs > without breaking the API. And so we can pull a neat trick: adding > Lockdep support into Arc<T>. This catches cases where the Arc Drop > implementation could create a locking correctness violation only when > the reference count drops to 0 at that particular drop site, which is > otherwise not detectable unless that condition actually happens at > runtime. Since Drop is "magic" in Rust and Drop codepaths very difficult > to audit, this helps a lot. > > For the initial RFC, this implements the new API only for Mutex. If this > looks good, I can extend it to CondVar & friends in the next version. > This series also folds in a few related minor dependencies / changes > (like the pin_init mutex stuff). I'm not convinced that this is the right compromise. Moving lockdep class creation to runtime sounds unfortunate, especially since this makes them fallible due to memory allocations (I think?). I would be inclined to keep using macros for this. Alice
On 14/07/2023 19.13, Alice Ryhl wrote: > Asahi Lina <lina@asahilina.net> writes: >> Begone, lock classes! >> >> As discussed in meetings/etc, we would really like to support implicit >> lock class creation for Rust code. Right now, lock classes are created >> using macros and passed around (similar to C). Unfortunately, Rust >> macros don't look like Rust functions, which means adding lockdep to a >> type is a breaking API change. This makes Rust mutex creation rather >> ugly, with the new_mutex!() macro and friends. >> >> Implicit lock classes have to be unique per instantiation code site. >> Notably, with Rust generics and monomorphization, this is not the same >> as unique per generated code instance. If this weren't the case, we >> could use inline functions and asm!() magic to try to create lock >> classes that have the right uniqueness semantics. But that doesn't work, >> since it would create too many lock classes for the same actual lock >> creation in the source code. >> >> But Rust does have one trick we can use: it can track the caller >> location (as file:line:column), across multiple functions. This works >> using an implicit argument that gets passed around, which is exactly the >> thing we do for lock classes. The tricky bit is that, while the value of >> these Location objects has the semantics we want (unique value per >> source code location), there is no guarantee that they are deduplicated >> in memory. >> >> So we use a hash table, and map Location values to lock classes. Et >> voila, implicit lock class support! >> >> This lets us clean up the Mutex & co APIs and make them look a lot more >> Rust-like, but it also means we can now throw Lockdep into more APIs >> without breaking the API. And so we can pull a neat trick: adding >> Lockdep support into Arc<T>. This catches cases where the Arc Drop >> implementation could create a locking correctness violation only when >> the reference count drops to 0 at that particular drop site, which is >> otherwise not detectable unless that condition actually happens at >> runtime. Since Drop is "magic" in Rust and Drop codepaths very difficult >> to audit, this helps a lot. >> >> For the initial RFC, this implements the new API only for Mutex. If this >> looks good, I can extend it to CondVar & friends in the next version. >> This series also folds in a few related minor dependencies / changes >> (like the pin_init mutex stuff). > > I'm not convinced that this is the right compromise. Moving lockdep > class creation to runtime sounds unfortunate, especially since this > makes them fallible due to memory allocations (I think?). > > I would be inclined to keep using macros for this. Most people were very enthusiastic about this change in the meetings... it wasn't even my own idea ^^ I don't think the fallibility is an issue. Lockdep is a debugging tool, and it doesn't have to handle all possible circumstances perfectly. If you are debugging normal lock issues you probably shouldn't be running out of RAM, and if you are debugging OOM situations the lock keys would normally have been created long before you reach an OOM situation, since they would be created the first time a relevant lock class is used. More objects of the same class don't cause any more allocations. And the code has a fallback for the OOM case, where it just uses the Location object as a static lock class. That's not ideal and degrades the quality of the lockdep results, but it shouldn't completely break anything. The advantages of being able to throw lockdep checking into arbitrary types, like the Arc<T> thing, are pretty significant. It closes a major correctness checking issue we have with Rust and its automagic Drop implementations that are almost impossible to properly audit for potential locking issues. I think that alone makes this worth it, even if you don't use it for normal mutex creation... ~~ Lina
Asahi Lina <lina@asahilina.net> writes: > On 14/07/2023 19.13, Alice Ryhl wrote: > > Asahi Lina <lina@asahilina.net> writes: > >> Begone, lock classes! > >> > >> As discussed in meetings/etc, we would really like to support implicit > >> lock class creation for Rust code. Right now, lock classes are created > >> using macros and passed around (similar to C). Unfortunately, Rust > >> macros don't look like Rust functions, which means adding lockdep to a > >> type is a breaking API change. This makes Rust mutex creation rather > >> ugly, with the new_mutex!() macro and friends. > >> > >> Implicit lock classes have to be unique per instantiation code site. > >> Notably, with Rust generics and monomorphization, this is not the same > >> as unique per generated code instance. If this weren't the case, we > >> could use inline functions and asm!() magic to try to create lock > >> classes that have the right uniqueness semantics. But that doesn't work, > >> since it would create too many lock classes for the same actual lock > >> creation in the source code. > >> > >> But Rust does have one trick we can use: it can track the caller > >> location (as file:line:column), across multiple functions. This works > >> using an implicit argument that gets passed around, which is exactly the > >> thing we do for lock classes. The tricky bit is that, while the value of > >> these Location objects has the semantics we want (unique value per > >> source code location), there is no guarantee that they are deduplicated > >> in memory. > >> > >> So we use a hash table, and map Location values to lock classes. Et > >> voila, implicit lock class support! > >> > >> This lets us clean up the Mutex & co APIs and make them look a lot more > >> Rust-like, but it also means we can now throw Lockdep into more APIs > >> without breaking the API. And so we can pull a neat trick: adding > >> Lockdep support into Arc<T>. This catches cases where the Arc Drop > >> implementation could create a locking correctness violation only when > >> the reference count drops to 0 at that particular drop site, which is > >> otherwise not detectable unless that condition actually happens at > >> runtime. Since Drop is "magic" in Rust and Drop codepaths very difficult > >> to audit, this helps a lot. > >> > >> For the initial RFC, this implements the new API only for Mutex. If this > >> looks good, I can extend it to CondVar & friends in the next version. > >> This series also folds in a few related minor dependencies / changes > >> (like the pin_init mutex stuff). > > > > I'm not convinced that this is the right compromise. Moving lockdep > > class creation to runtime sounds unfortunate, especially since this > > makes them fallible due to memory allocations (I think?). > > > > I would be inclined to keep using macros for this. > > Most people were very enthusiastic about this change in the meetings... > it wasn't even my own idea ^^ I don't think I was in that meeting. Anyway, > I don't think the fallibility is an issue. Lockdep is a debugging tool, > and it doesn't have to handle all possible circumstances perfectly. If > you are debugging normal lock issues you probably shouldn't be running > out of RAM, and if you are debugging OOM situations the lock keys would > normally have been created long before you reach an OOM situation, since > they would be created the first time a relevant lock class is used. More > objects of the same class don't cause any more allocations. And the code > has a fallback for the OOM case, where it just uses the Location object > as a static lock class. That's not ideal and degrades the quality of the > lockdep results, but it shouldn't completely break anything. If you have a fallback when the allocation fails, that helps ... You say that Location objects are not necessarily unique per file location. In practice, how often are they not unique? Always just using the Location object as a static lock class seems like it would significantly simplify this proposal. > The advantages of being able to throw lockdep checking into arbitrary > types, like the Arc<T> thing, are pretty significant. It closes a major > correctness checking issue we have with Rust and its automagic Drop > implementations that are almost impossible to properly audit for > potential locking issues. I think that alone makes this worth it, even > if you don't use it for normal mutex creation... I do agree that there is value in being able to more easily detect potential deadlocks involving destructors of ref-counted values. I once had a case of that myself, though lockdep was able to catch it without this change because it saw the refcount hit zero in the right place. Alice
On Fri, Jul 14, 2023 at 01:59:26PM +0000, Alice Ryhl wrote: > Asahi Lina <lina@asahilina.net> writes: > > On 14/07/2023 19.13, Alice Ryhl wrote: > > > Asahi Lina <lina@asahilina.net> writes: > > >> Begone, lock classes! > > >> > > >> As discussed in meetings/etc, we would really like to support implicit > > >> lock class creation for Rust code. Right now, lock classes are created Thanks for looking into this! Could you also copy locking maintainers in the next version? > > >> using macros and passed around (similar to C). Unfortunately, Rust > > >> macros don't look like Rust functions, which means adding lockdep to a > > >> type is a breaking API change. This makes Rust mutex creation rather > > >> ugly, with the new_mutex!() macro and friends. > > >> > > >> Implicit lock classes have to be unique per instantiation code site. > > >> Notably, with Rust generics and monomorphization, this is not the same > > >> as unique per generated code instance. If this weren't the case, we > > >> could use inline functions and asm!() magic to try to create lock > > >> classes that have the right uniqueness semantics. But that doesn't work, > > >> since it would create too many lock classes for the same actual lock > > >> creation in the source code. > > >> > > >> But Rust does have one trick we can use: it can track the caller > > >> location (as file:line:column), across multiple functions. This works > > >> using an implicit argument that gets passed around, which is exactly the > > >> thing we do for lock classes. The tricky bit is that, while the value of > > >> these Location objects has the semantics we want (unique value per > > >> source code location), there is no guarantee that they are deduplicated > > >> in memory. > > >> > > >> So we use a hash table, and map Location values to lock classes. Et > > >> voila, implicit lock class support! > > >> > > >> This lets us clean up the Mutex & co APIs and make them look a lot more > > >> Rust-like, but it also means we can now throw Lockdep into more APIs > > >> without breaking the API. And so we can pull a neat trick: adding > > >> Lockdep support into Arc<T>. This catches cases where the Arc Drop > > >> implementation could create a locking correctness violation only when > > >> the reference count drops to 0 at that particular drop site, which is > > >> otherwise not detectable unless that condition actually happens at > > >> runtime. Since Drop is "magic" in Rust and Drop codepaths very difficult > > >> to audit, this helps a lot. > > >> > > >> For the initial RFC, this implements the new API only for Mutex. If this > > >> looks good, I can extend it to CondVar & friends in the next version. > > >> This series also folds in a few related minor dependencies / changes > > >> (like the pin_init mutex stuff). > > > > > > I'm not convinced that this is the right compromise. Moving lockdep > > > class creation to runtime sounds unfortunate, especially since this > > > makes them fallible due to memory allocations (I think?). > > > > > > I would be inclined to keep using macros for this. > > > > Most people were very enthusiastic about this change in the meetings... > > it wasn't even my own idea ^^ > > I don't think I was in that meeting. Anyway, > > > I don't think the fallibility is an issue. Lockdep is a debugging tool, > > and it doesn't have to handle all possible circumstances perfectly. If > > you are debugging normal lock issues you probably shouldn't be running > > out of RAM, and if you are debugging OOM situations the lock keys would > > normally have been created long before you reach an OOM situation, since > > they would be created the first time a relevant lock class is used. More > > objects of the same class don't cause any more allocations. And the code > > has a fallback for the OOM case, where it just uses the Location object > > as a static lock class. That's not ideal and degrades the quality of the > > lockdep results, but it shouldn't completely break anything. > > If you have a fallback when the allocation fails, that helps ... > > You say that Location objects are not necessarily unique per file > location. In practice, how often are they not unique? Always just using > the Location object as a static lock class seems like it would > significantly simplify this proposal. > Agreed. For example, `caller_lock_class_inner` has a Mutex critical section in it (for the hash table synchronization), that makes it impossible to be called in preemption disabled contexts, which limits the usage. Regards, Boqun > > The advantages of being able to throw lockdep checking into arbitrary > > types, like the Arc<T> thing, are pretty significant. It closes a major > > correctness checking issue we have with Rust and its automagic Drop > > implementations that are almost impossible to properly audit for > > potential locking issues. I think that alone makes this worth it, even > > if you don't use it for normal mutex creation... > > I do agree that there is value in being able to more easily detect > potential deadlocks involving destructors of ref-counted values. I once > had a case of that myself, though lockdep was able to catch it without > this change because it saw the refcount hit zero in the right place. > > Alice >
On Fri, 14 Jul 2023 13:59:26 +0000 Alice Ryhl <aliceryhl@google.com> wrote: > Asahi Lina <lina@asahilina.net> writes: > > On 14/07/2023 19.13, Alice Ryhl wrote: > > > Asahi Lina <lina@asahilina.net> writes: > > >> Begone, lock classes! > > >> > > >> As discussed in meetings/etc, we would really like to support implicit > > >> lock class creation for Rust code. Right now, lock classes are created > > >> using macros and passed around (similar to C). Unfortunately, Rust > > >> macros don't look like Rust functions, which means adding lockdep to a > > >> type is a breaking API change. This makes Rust mutex creation rather > > >> ugly, with the new_mutex!() macro and friends. > > >> > > >> Implicit lock classes have to be unique per instantiation code site. > > >> Notably, with Rust generics and monomorphization, this is not the same > > >> as unique per generated code instance. If this weren't the case, we > > >> could use inline functions and asm!() magic to try to create lock > > >> classes that have the right uniqueness semantics. But that doesn't work, > > >> since it would create too many lock classes for the same actual lock > > >> creation in the source code. > > >> > > >> But Rust does have one trick we can use: it can track the caller > > >> location (as file:line:column), across multiple functions. This works > > >> using an implicit argument that gets passed around, which is exactly the > > >> thing we do for lock classes. The tricky bit is that, while the value of > > >> these Location objects has the semantics we want (unique value per > > >> source code location), there is no guarantee that they are deduplicated > > >> in memory. > > >> > > >> So we use a hash table, and map Location values to lock classes. Et > > >> voila, implicit lock class support! > > >> > > >> This lets us clean up the Mutex & co APIs and make them look a lot more > > >> Rust-like, but it also means we can now throw Lockdep into more APIs > > >> without breaking the API. And so we can pull a neat trick: adding > > >> Lockdep support into Arc<T>. This catches cases where the Arc Drop > > >> implementation could create a locking correctness violation only when > > >> the reference count drops to 0 at that particular drop site, which is > > >> otherwise not detectable unless that condition actually happens at > > >> runtime. Since Drop is "magic" in Rust and Drop codepaths very difficult > > >> to audit, this helps a lot. > > >> > > >> For the initial RFC, this implements the new API only for Mutex. If this > > >> looks good, I can extend it to CondVar & friends in the next version. > > >> This series also folds in a few related minor dependencies / changes > > >> (like the pin_init mutex stuff). > > > > > > I'm not convinced that this is the right compromise. Moving lockdep > > > class creation to runtime sounds unfortunate, especially since this > > > makes them fallible due to memory allocations (I think?). > > > > > > I would be inclined to keep using macros for this. > > > > Most people were very enthusiastic about this change in the meetings... > > it wasn't even my own idea ^^ > > I don't think I was in that meeting. Anyway, Just for some contexts. This idea has been discussed multiple times. The earliest discussion that I can recall is from a tea-break-time discussion in Kangrejos 2022. It was brought up recently in a discussion related to DRM, and the consensus was that it's definitely a idea worth exploring. > > > I don't think the fallibility is an issue. Lockdep is a debugging tool, > > and it doesn't have to handle all possible circumstances perfectly. If > > you are debugging normal lock issues you probably shouldn't be running > > out of RAM, and if you are debugging OOM situations the lock keys would > > normally have been created long before you reach an OOM situation, since > > they would be created the first time a relevant lock class is used. More > > objects of the same class don't cause any more allocations. And the code > > has a fallback for the OOM case, where it just uses the Location object > > as a static lock class. That's not ideal and degrades the quality of the > > lockdep results, but it shouldn't completely break anything. > > If you have a fallback when the allocation fails, that helps ... I am pretty sure lockdep needs to do some internal allocation anyway because only address matters for lock class keys. So some extra allocation probably is fine... > > You say that Location objects are not necessarily unique per file > location. In practice, how often are they not unique? Always just using > the Location object as a static lock class seems like it would > significantly simplify this proposal. Location objects are constants, so they are not guaranteed to have a fixed address. With inlining and generics you can very easily get multiple instances of it. That said, their address is also not significant, so LLVM is pretty good at merging them back to one single address, **if everything is linked statically**. The merging is an optimisation, and is far from guaranteed. With kernel modules, which effectively is dynamic linking, the address of `Location` *will* be duplicated if the function invoking a `#[track_caller]` function is inlined. An idea was flared when I discussed this with Josh Triplett in last Kangrejos, that it might be possible to make `Location` generated by compiler be `static` rather than just normal constants, and then we can ensure that the address is unique. I tried to prototype this idea but it didn't seem to work very well because currently you can use `#[track_caller]` in a const fn but can't refer to statics in a const fn, so it's a bit hard to desugar. I am pretty sure there are ways around it, but someone would need to implement it :) So TL;DR: while in many cases the address is unique, it's far from a guarantee. It might be possible to guarantee uniqueness but that requires compiler changes. > > > The advantages of being able to throw lockdep checking into arbitrary > > types, like the Arc<T> thing, are pretty significant. It closes a major > > correctness checking issue we have with Rust and its automagic Drop > > implementations that are almost impossible to properly audit for > > potential locking issues. I think that alone makes this worth it, even > > if you don't use it for normal mutex creation... > > I do agree that there is value in being able to more easily detect > potential deadlocks involving destructors of ref-counted values. I once > had a case of that myself, though lockdep was able to catch it without > this change because it saw the refcount hit zero in the right place. > > Alice >
On 15/07/2023 00.21, Boqun Feng wrote: > On Fri, Jul 14, 2023 at 01:59:26PM +0000, Alice Ryhl wrote: >> Asahi Lina <lina@asahilina.net> writes: >>> On 14/07/2023 19.13, Alice Ryhl wrote: >>>> Asahi Lina <lina@asahilina.net> writes: >>>>> Begone, lock classes! >>>>> >>>>> As discussed in meetings/etc, we would really like to support implicit >>>>> lock class creation for Rust code. Right now, lock classes are created > > Thanks for looking into this! Could you also copy locking maintainers in > the next version? Sure! Sorry, I totally forgot that I needed to do that manually since b4 doesn't know about rust->C relations... > >>>>> using macros and passed around (similar to C). Unfortunately, Rust >>>>> macros don't look like Rust functions, which means adding lockdep to a >>>>> type is a breaking API change. This makes Rust mutex creation rather >>>>> ugly, with the new_mutex!() macro and friends. >>>>> >>>>> Implicit lock classes have to be unique per instantiation code site. >>>>> Notably, with Rust generics and monomorphization, this is not the same >>>>> as unique per generated code instance. If this weren't the case, we >>>>> could use inline functions and asm!() magic to try to create lock >>>>> classes that have the right uniqueness semantics. But that doesn't work, >>>>> since it would create too many lock classes for the same actual lock >>>>> creation in the source code. >>>>> >>>>> But Rust does have one trick we can use: it can track the caller >>>>> location (as file:line:column), across multiple functions. This works >>>>> using an implicit argument that gets passed around, which is exactly the >>>>> thing we do for lock classes. The tricky bit is that, while the value of >>>>> these Location objects has the semantics we want (unique value per >>>>> source code location), there is no guarantee that they are deduplicated >>>>> in memory. >>>>> >>>>> So we use a hash table, and map Location values to lock classes. Et >>>>> voila, implicit lock class support! >>>>> >>>>> This lets us clean up the Mutex & co APIs and make them look a lot more >>>>> Rust-like, but it also means we can now throw Lockdep into more APIs >>>>> without breaking the API. And so we can pull a neat trick: adding >>>>> Lockdep support into Arc<T>. This catches cases where the Arc Drop >>>>> implementation could create a locking correctness violation only when >>>>> the reference count drops to 0 at that particular drop site, which is >>>>> otherwise not detectable unless that condition actually happens at >>>>> runtime. Since Drop is "magic" in Rust and Drop codepaths very difficult >>>>> to audit, this helps a lot. >>>>> >>>>> For the initial RFC, this implements the new API only for Mutex. If this >>>>> looks good, I can extend it to CondVar & friends in the next version. >>>>> This series also folds in a few related minor dependencies / changes >>>>> (like the pin_init mutex stuff). >>>> >>>> I'm not convinced that this is the right compromise. Moving lockdep >>>> class creation to runtime sounds unfortunate, especially since this >>>> makes them fallible due to memory allocations (I think?). >>>> >>>> I would be inclined to keep using macros for this. >>> >>> Most people were very enthusiastic about this change in the meetings... >>> it wasn't even my own idea ^^ >> >> I don't think I was in that meeting. Anyway, >> >>> I don't think the fallibility is an issue. Lockdep is a debugging tool, >>> and it doesn't have to handle all possible circumstances perfectly. If >>> you are debugging normal lock issues you probably shouldn't be running >>> out of RAM, and if you are debugging OOM situations the lock keys would >>> normally have been created long before you reach an OOM situation, since >>> they would be created the first time a relevant lock class is used. More >>> objects of the same class don't cause any more allocations. And the code >>> has a fallback for the OOM case, where it just uses the Location object >>> as a static lock class. That's not ideal and degrades the quality of the >>> lockdep results, but it shouldn't completely break anything. >> >> If you have a fallback when the allocation fails, that helps ... >> >> You say that Location objects are not necessarily unique per file >> location. In practice, how often are they not unique? Always just using >> the Location object as a static lock class seems like it would >> significantly simplify this proposal. If a generic type is instantiated from different crates (e.g. kernel crate and a driver), it creates separate Location objects. But we also have a bigger problem: this breaks module unload, since that leaves lock classes dangling. Though that is yet another discussion to have (Rust's lifetime semantics kind of break down when you can unload modules!). >> > > Agreed. For example, `caller_lock_class_inner` has a Mutex critical > section in it (for the hash table synchronization), that makes it > impossible to be called in preemption disabled contexts, which limits > the usage. Maybe we can just make it a spinlock? The critical section is very short for lock classes that already exist (just iterating over the hash bucket, which will almost always be length 1), so it's probably more efficient to do that than use a mutex anyway. Lockdep itself uses a single global spinlock for a bunch of stuff too. For the new class case it does do an allocation, but I think code probably shouldn't be creating locks and things like that with preemption disabled / in atomic context? That just seems like a recipe for trouble... though this ties into the whole execution context story for Rust, which we don't have a terribly good answer for yet, so I think it shouldn't block this approach. The macro style lock creation primitives still exist for code that really needs the static behavior. ~~ Lina
On Sat, Jul 15, 2023 at 03:25:54PM +0100, Gary Guo wrote: [...] > > > I don't think the fallibility is an issue. Lockdep is a debugging tool, > > > and it doesn't have to handle all possible circumstances perfectly. If > > > you are debugging normal lock issues you probably shouldn't be running > > > out of RAM, and if you are debugging OOM situations the lock keys would > > > normally have been created long before you reach an OOM situation, since > > > they would be created the first time a relevant lock class is used. More > > > objects of the same class don't cause any more allocations. And the code > > > has a fallback for the OOM case, where it just uses the Location object > > > as a static lock class. That's not ideal and degrades the quality of the > > > lockdep results, but it shouldn't completely break anything. > > > > If you have a fallback when the allocation fails, that helps ... > > I am pretty sure lockdep needs to do some internal allocation anyway > because only address matters for lock class keys. So some extra > allocation probably is fine... > Lockdep uses a few static arrays for its own allocation, but doesn't use "external" allocatin (i.e. kalloc() and its friends. IIUC, originally this has to do in this way to avoid recursive calls like: lockdep->slab->lockdep, but now lockdep has a recursion counter, that's not a problem any more. However, it's still better that lockdep can work on its own without relying on other components. Regards, Boqun