-
Notifications
You must be signed in to change notification settings - Fork 435
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
rust: add new api for initialization #909
Conversation
b77ee65
to
1029159
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just did a quick scan of the first few files and left a few comments.
This is a huge improvement over what we have now.
(I'll allocate some time to go over the rest.)
drivers/android/context.rs
Outdated
// SAFETY: Init is called below. | ||
manager: unsafe { | ||
Mutex::new(Manager { | ||
let ctx = UniqueArc::pin_init::<core::convert::Infallible>(pin_init!(Self { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The reason this was a Pin<UniqueArc>
originally was because we had to initialise the mutex in it. Since we don't need this anymore, couldn't this just be an Arc
?
This also feels a bit too verbose (e.g., having to specify core::convert::Infallible
). I haven't looked at the details yet, but it would be great if we could avoid this.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Currently I have not implemented InPlaceInit
for Arc
, since it does not fit the API (you can only call pin_init
on it). But since we have full control over it, we could just add it as a method. Will do so 👍
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
And yeah sadly we have to specify the error type.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is just an unfortunate result of using ?
(which is why I chose to not have ?
in my pin-init
crate but instead make error propagation automatic).
Basically since all error return paths use ?
which expands to a From::from
, the error type never unifies with anything and stay as an inference variable, hence the explicit specification.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If we had the never type this would be a much more bearable ::<!>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does it work with the never type (even if it is not stable), or are you triggering some compiler bug etc. there?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It works with the never type
/// The caller must call `NodeDeath::init` before using the notification object. | ||
pub(crate) unsafe fn new(node: Arc<Node>, process: Arc<Process>, cookie: usize) -> Self { | ||
Self { | ||
#[allow(clippy::new_ret_no_self)] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there a way to avoid having to disable this for every function where we return impl InInit<Self>
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not naming it new
? I am not sure if we should even name them new, maybe new_in_place
or something might be more expressive. AFAIK we could add #![allow(clippy::new_ret_no_self)]
to lib.rs
, but then it would not warn any longer in other places... Maybe we could get in touch with the clippy folks and ask for some workaround (I am not aware of any currently). There is precedent for this: rust-lang/rust-clippy#3313
drivers/android/process.rs
Outdated
self.clone(), | ||
cookie, | ||
)) | ||
.map_err(|(i, _)| i)?, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What is the pair returned here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It is (E, UniqueArc<T>)
and E
is Infallible
.
the function called is UniqueArc::<MaybeUninit<T>>::pin_init_now(self, impl PinInit<T, E>) -> Result<UniqueArc<T>, (E, Self)>
and if the initializer fails I wanted to give a way to still keep the allocated memory.
This is the only place this function is used, because I did not want to change the code. I thought that there might be a reason that the memory is first allocated and then some checks are run (lines 701-707). If we can refactor this code to use UniqueArc::pin_init
then we could remove the pin_init_now
function (which I would prefer).
node_ref: None, | ||
stack_next: None, | ||
from: from.clone(), | ||
to, | ||
code: tr.code, | ||
flags: tr.flags, | ||
data_size: tr.data_size as _, | ||
data_size: tr.data_size as usize, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this needed?
(To clarify, I'm not going into the question of whether it is better/worse to be explicit about the type, we can discuss that elsewhere. I'm asking if the new macros made things such that the compiler can't infer the types anymore.)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes this is needed, otherwise we get a type inference error (have not investigated why).
What is the reason that the CI checks were cancelled? |
Not sure. Let me ask them to rerun. |
My branch that uses |
@y86-dev it seems that the init syntax has been further changed within the past couple of days? The surface syntax is very similar to One difference is that the thing within |
Not sure which part of the syntax you are referring to... If you look at https://github.com/y86-dev/pinned-init/blame/main/examples/mutex.rs#L68 that has not changed for 22 days.
Yeah, I need that for the struct initializer (otherwise again some inference error). |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Some nits from a quick look for the next time you force-push.
By the way, the commit splitting looks fine to me! Why do you want to squash the two commits?
As for the commit message and the:
TODO: describe the purpose and functionality of the API better.
I am guessing you may be dreading explaining everything there :) If that is the case, do not worry too much about explaining everything in the commit message. In particular, you don't need to repeat the docs/API since it will be in the source code. Instead, try to focus on the "why do we want/need this change".
drivers/android/context.rs
Outdated
// SAFETY: Init is called below. | ||
manager: unsafe { | ||
Mutex::new(Manager { | ||
let ctx = UniqueArc::pin_init::<core::convert::Infallible>(pin_init!(Self { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does it work with the never type (even if it is not stable), or are you triggering some compiler bug etc. there?
I plan to do normal commits to have a better review and will rebase at the end |
Oh, sorry! I was under the impression that the syntax on the README was the latest syntax when I checked last time.
That's quite unfortunate. I am a big fan of your approach (todo struct, and the raw-pointer-based API), but I think the current state as it is requires too many explicit annotations. |
Did you mean the syntax with |
One idea about solving the error type inference problem: given that in kernel we usually only use two error types, We can call the macros and functions like this:
Of course this doesn't help with the inference problem at the struct and field... |
I am not sure... Users might want to have custom errors that are ZSTs or have more options than There could be a situation where you want to initialize something |
Another option to deal with error type inference would be to allow explicitly specifying the error type in the macro if it couldn't be inferred. |
Summary of the last few commits (up to the last force-push):
|
rebased commits again that had been accumulating. Also fixed some unsoundness with |
30dc92f
to
b5b8cef
Compare
rust/kernel/sync/arc.rs
Outdated
// assert that there are only two fields: refcount and data (done in a closure to avoid | ||
// overflowing the stack in debug mode with a big `T`) | ||
#[allow(unreachable_code, clippy::diverging_sub_expression)] | ||
let _check = || { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't feel that this check is necessary given that the definition of ArcInner
is in the same file.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What do you think of the implementation now?
/// | ||
/// The pointer supplied is valid. | ||
pub unsafe fn raw_get(this: *const Self) -> *mut T { | ||
UnsafeCell::raw_get(addr_of!((*this).0).cast::<UnsafeCell<T>>()) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe
UnsafeCell::raw_get(this.cast::<UnsafeCell<T>>())
? Opaque
and MaybeUninit
are both #[repr(transparent)]
so we can just cast the pointer directly.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Seems reasonable, it was a bit unclear to me if we guarantee that Opaque
will always be transparent
(I guess changing it would also require changing this method, but wanted to be careful). To be clear: I do not think that there is a reason for making Opaque
not transparent, so we could just clarify that in the documentation.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Normally, the UnsafeCell
would go outside the MaybeUninit
.
$($inner)* | ||
} | ||
|
||
fn __ensure_no_unsafe_op_in_drop($self: $st) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think this is necessary, we enable unsafe_op_in_unsafe_fn
globally. If you want to be extra careful you can generate a #[forbid(unsafe_op_in_unsafe_fn)]
on the drop function.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You can still silence forbid using --cap-lints allow.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we should be extra careful here.
/// The caller must call `NodeDeath::init` before using the notification object. | ||
pub(crate) unsafe fn new(node: Arc<Node>, process: Arc<Process>, cookie: usize) -> Self { | ||
Self { | ||
#[allow(clippy::new_ret_no_self)] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We probably want to disable this globally until rust-lang/rust-clippy#7344 is fixed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Filed rust-lang/rust-clippy#9733 to fix the false positive.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should I just add #![allow(clippy::new_ret_no_self)]
to lib.rs
?
rust/kernel/sync/condvar.rs
Outdated
_pin: PhantomPinned, | ||
#[allow(clippy::new_ret_no_self)] | ||
pub const fn new(name: &'static CStr, key: &'static LockClassKey) -> impl PinInit<Self> { | ||
fn init_wait_list( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What's the rationale behind having a new function instead of just having
wait_list: unsafe { init::pin_init_from_closure(|| { ... }) }
down there?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
here would be the closure version:
pin_init!(Self {
wait_list: unsafe {
init::pin_init_from_closure::<_, core::convert::Infallible>(move |slot| {
bindings::__init_waitqueue_head(
Opaque::raw_get(slot),
name.as_char_ptr(),
key.get(),
);
Ok(())
})
},
_pin: PhantomPinned,
})
(yes we need to specify the error type 🙄)
Not sure if that is more readable... I could probably create a more general function that would handle this, so along the lines of:
unsafe fn ffi_init1<T, P1>(fun: unsafe fn(*mut T, P1), param1: P1) -> impl PinInit<Opaque<T>>;
unsafe fn ffi_init2<T, P1, P2>(fun: unsafe fn(*mut T, P1), param1: P1, param2: P2) -> impl PinInit<Opaque<T>>;
unsafe fn ffi_init3<T, P1, P2, P2>(fun: unsafe fn(*mut T, P1), param1: P1, param2: P2, param3: P3) -> impl PinInit<Opaque<T>>;
let mut pinned_drop_idx = None; | ||
for (i, tt) in toks.iter().enumerate() { | ||
match tt { | ||
TokenTree::Punct(p) if p.as_char() == '<' => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Perhaps the angle bracket matching code can be extracted to a function and shared between both macros.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This seems a bit difficult, because in the code for pin_project
I am going through the generics list and I am searching for the identifiers. So that code seems a bit too specific to extract that and keep the functionality...
I can still create the function for use in other places though...
Also added `assume_init` to `UniqueArc<MaybeUninit<T>>`. It assumes that its contents have been initialized and returns `UniqueArc<T>`. `try_new_uninit` is needed, because `try_new(MaybeUninit::uninit())` actually allocates memory on the stack and may result in a stack overflow if `T` is large in size. `try_new_uninit` has been implemented in such a way that this cannot happen. Signed-off-by: Benno Lossin <y86-dev@protonmail.com>
This function does exactly the same thing as `UnsafeCell::raw_get` [1]. It exists to avoid creating intermediate references. Link: https://doc.rust-lang.org/core/cell/struct.UnsafeCell.html#method.raw_get [1] Signed-off-by: Benno Lossin <y86-dev@protonmail.com>
This allows proc macros to refer to `::kernel`. This syntax is needed in order to ensure that we refer to the true kernel crate instead of a local module. Signed-off-by: Benno Lossin <y86-dev@protonmail.com>
cf70f5e
to
9e122cc
Compare
This API is used to initialize pinned structs. Before we had to use `unsafe` to first call `Mutex::new` with the promise that we would call `Mutex::init` later (which also requires `unsafe`, because it first needs a pin-projection). This API merges those two functions into one such that it is now a safe operation for the user. It is implemented using a combination of things: - declarative macros used to create initializers (`pin_init!`/`init!`), - declarative macro used to inform about the pinned fields (`pin_project!`), - proc macro shim delegating to the previously mentioned declarative macro (the proc macro does a bit of generics parsing, it is `#[pin_project]`), - traits and a couple of functions to create basic initializers. For details, please read the documentation at [1]. Link: https://rust-for-linux.github.io/docs/kernel/init/index.html [1] Signed-off-by: Benno Lossin <y86-dev@protonmail.com>
This patch changes the API and internal logic such that the new init API is used. This change results in a lot less `unsafe`, because the new API is a safe abstraction over the unsafe interior. Signed-off-by: Benno Lossin <y86-dev@protonmail.com>
I had a brief look and it does indeed seem like the inference will be very limited with current design. However I think maybe we can have slightly different syntax for direct initialization vs in-place initialization, e.g. pin_init!(Self {
foo: 1, // this is direct initialization
mutex <- new_mutex!(...), // this is in-place initialization
}) this way type inference can work properly for direct initialization, and we also have a clear indication of which one is pinned initialized and which one is not (could be a good thing potentially?) |
This was one of the intermediate states I had, but I changed it, because at kangrejos there were some worries about non-standard syntax. I think that it makes it easier to understand (and as you also said makes inference better) how things are initialized. |
closed since https://lore.kernel.org/rust-for-linux/20230408122429.1103522-1-y86-dev@protonmail.com/ was merged |
This is my newest version of the pinned-initialization API. You can find it as a stand-alone crate here: pinned-init.
I plan to squash 276ae43 onto 50f2121. I separated the commits for better earlier review.
I also need to provide a better commit message better explaining the API (linking to my blog might help, but it is a long read, so...).
I removed all
unsafe
initialization API from thesync
module. If we still want to keep the old API and we want to deprecate it instead just let me know.Closes #772