mirror of
https://github.com/torvalds/linux.git
synced 2025-08-15 14:11:42 +02:00
pin-init changes for v6.17
Added: - 'impl<T, E> [Pin]Init<T, E> for Result<T, E>', so results are now (pin-)initializers. - 'Zeroable::init_zeroed()' delegating to 'init_zeroed()'. - New 'zeroed()', a safe version of 'mem::zeroed()' and also provide it via 'Zeroable::zeroed()'. - Implement 'Zeroable' for 'Option<&T>' and 'Option<&mut T>'. - Implement 'Zeroable' for 'Option<[unsafe] [extern "abi"] fn(...args...) -> ret>' for '"Rust"' and '"C"' ABIs and up to 20 arguments. Changed: - Blanket impls of 'Init' and 'PinInit' from 'impl<T, E> [Pin]Init<T, E> for T' to 'impl<T> [Pin]Init<T> for T'. - Renamed 'zeroed()' to 'init_zeroed()'. Upstream dev news: - More CI improvements to deny warnings, use '--all-targets'. Also check the synchronization status of the two '-next' branches in upstream and the kernel. -----BEGIN PGP SIGNATURE----- iIgEABYKADAWIQQjEG/HT3UeYLJybLTomd21rZaLygUCaGqd4RIcbG9zc2luQGtl cm5lbC5vcmcACgkQ6Jndta2Wi8qKagD/aBH4iwbal0DHbYx36TMfkvunp6pkKAo1 uu7qbmNwMMwBAKuRb41xA2AvllcYy/aMsb5H00o3iVLOzpMUHNnOFdIJ =0DbP -----END PGP SIGNATURE----- Merge tag 'pin-init-v6.17' of https://github.com/Rust-for-Linux/linux into rust-next Pull pin-init updates from Benno Lossin: "Added: - 'impl<T, E> [Pin]Init<T, E> for Result<T, E>', so results are now (pin-)initializers. - 'Zeroable::init_zeroed()' delegating to 'init_zeroed()'. - New 'zeroed()', a safe version of 'mem::zeroed()' and also provide it via 'Zeroable::zeroed()'. - Implement 'Zeroable' for 'Option<&T>' and 'Option<&mut T>'. - Implement 'Zeroable' for 'Option<[unsafe] [extern "abi"] fn(...args...) -> ret>' for '"Rust"' and '"C"' ABIs and up to 20 arguments. Changed: - Blanket impls of 'Init' and 'PinInit' from 'impl<T, E> [Pin]Init<T, E> for T' to 'impl<T> [Pin]Init<T> for T'. - Renamed 'zeroed()' to 'init_zeroed()'. Upstream dev news: - More CI improvements to deny warnings, use '--all-targets'. Also check the synchronization status of the two '-next' branches in upstream and the kernel." Acked-by: Andreas Hindborg <a.hindborg@kernel.org> * tag 'pin-init-v6.17' of https://github.com/Rust-for-Linux/linux: rust: pin-init: examples, tests: use `ignore` instead of conditionally compiling tests rust: init: remove doctest's `Error::from_errno` workaround rust: init: re-enable doctests rust: pin-init: implement `ZeroableOption` for function pointers with up to 20 arguments rust: pin-init: change `impl Zeroable for Option<NonNull<T>>` to `ZeroableOption for NonNull<T>` rust: pin-init: implement `ZeroableOption` for `&T` and `&mut T` rust: pin-init: add `zeroed()` & `Zeroable::zeroed()` functions rust: pin-init: add `Zeroable::init_zeroed` rust: pin-init: rename `zeroed` to `init_zeroed` rust: pin-init: feature-gate the `stack_init_reuse` test on the `std` feature rust: pin-init: examples: pthread_mutex: disable the main test for miri rust: pin-init: examples, tests: add conditional compilation in order to compile under any feature combination rust: pin-init: change blanket impls for `[Pin]Init` and add one for `Result<T, E>` rust: pin-init: improve safety documentation for `impl<T> [Pin]Init<T> for T`
This commit is contained in:
commit
e8fa0481ea
12 changed files with 287 additions and 148 deletions
|
@ -9,7 +9,7 @@ use core::pin::Pin;
|
|||
use crate::{
|
||||
bindings,
|
||||
block::mq::{operations::OperationsVTable, request::RequestDataWrapper, Operations},
|
||||
error,
|
||||
error::{self, Result},
|
||||
prelude::try_pin_init,
|
||||
types::Opaque,
|
||||
};
|
||||
|
@ -41,7 +41,7 @@ impl<T: Operations> TagSet<T> {
|
|||
// SAFETY: `blk_mq_tag_set` only contains integers and pointers, which
|
||||
// all are allowed to be 0.
|
||||
let tag_set: bindings::blk_mq_tag_set = unsafe { core::mem::zeroed() };
|
||||
let tag_set = core::mem::size_of::<RequestDataWrapper>()
|
||||
let tag_set: Result<_> = core::mem::size_of::<RequestDataWrapper>()
|
||||
.try_into()
|
||||
.map(|cmd_size| {
|
||||
bindings::blk_mq_tag_set {
|
||||
|
@ -56,12 +56,14 @@ impl<T: Operations> TagSet<T> {
|
|||
nr_maps: num_maps,
|
||||
..tag_set
|
||||
}
|
||||
});
|
||||
})
|
||||
.map(Opaque::new)
|
||||
.map_err(|e| e.into());
|
||||
|
||||
try_pin_init!(TagSet {
|
||||
inner <- PinInit::<_, error::Error>::pin_chain(Opaque::new(tag_set?), |tag_set| {
|
||||
inner <- tag_set.pin_chain(|tag_set| {
|
||||
// SAFETY: we do not move out of `tag_set`.
|
||||
let tag_set = unsafe { Pin::get_unchecked_mut(tag_set) };
|
||||
let tag_set: &mut Opaque<_> = unsafe { Pin::get_unchecked_mut(tag_set) };
|
||||
// SAFETY: `tag_set` is a reference to an initialized `blk_mq_tag_set`.
|
||||
error::to_result( unsafe { bindings::blk_mq_alloc_tag_set(tag_set.get())})
|
||||
}),
|
||||
|
|
|
@ -151,7 +151,7 @@ impl<Data> Subsystem<Data> {
|
|||
data: impl PinInit<Data, Error>,
|
||||
) -> impl PinInit<Self, Error> {
|
||||
try_pin_init!(Self {
|
||||
subsystem <- pin_init::zeroed().chain(
|
||||
subsystem <- pin_init::init_zeroed().chain(
|
||||
|place: &mut Opaque<bindings::configfs_subsystem>| {
|
||||
// SAFETY: We initialized the required fields of `place.group` above.
|
||||
unsafe {
|
||||
|
@ -261,7 +261,7 @@ impl<Data> Group<Data> {
|
|||
data: impl PinInit<Data, Error>,
|
||||
) -> impl PinInit<Self, Error> {
|
||||
try_pin_init!(Self {
|
||||
group <- pin_init::zeroed().chain(|v: &mut Opaque<bindings::config_group>| {
|
||||
group <- pin_init::init_zeroed().chain(|v: &mut Opaque<bindings::config_group>| {
|
||||
let place = v.get();
|
||||
let name = name.as_bytes_with_nul().as_ptr();
|
||||
// SAFETY: It is safe to initialize a group once it has been zeroed.
|
||||
|
|
|
@ -29,15 +29,15 @@
|
|||
//!
|
||||
//! ## General Examples
|
||||
//!
|
||||
//! ```rust,ignore
|
||||
//! # #![allow(clippy::disallowed_names)]
|
||||
//! ```rust
|
||||
//! # #![expect(clippy::disallowed_names, clippy::undocumented_unsafe_blocks)]
|
||||
//! use kernel::types::Opaque;
|
||||
//! use pin_init::pin_init_from_closure;
|
||||
//!
|
||||
//! // assume we have some `raw_foo` type in C:
|
||||
//! #[repr(C)]
|
||||
//! struct RawFoo([u8; 16]);
|
||||
//! extern {
|
||||
//! extern "C" {
|
||||
//! fn init_foo(_: *mut RawFoo);
|
||||
//! }
|
||||
//!
|
||||
|
@ -66,25 +66,17 @@
|
|||
//! });
|
||||
//! ```
|
||||
//!
|
||||
//! ```rust,ignore
|
||||
//! # #![allow(unreachable_pub, clippy::disallowed_names)]
|
||||
//! ```rust
|
||||
//! # #![expect(unreachable_pub, clippy::disallowed_names)]
|
||||
//! use kernel::{prelude::*, types::Opaque};
|
||||
//! use core::{ptr::addr_of_mut, marker::PhantomPinned, pin::Pin};
|
||||
//! # mod bindings {
|
||||
//! # #![allow(non_camel_case_types)]
|
||||
//! # #![expect(non_camel_case_types, clippy::missing_safety_doc)]
|
||||
//! # pub struct foo;
|
||||
//! # pub unsafe fn init_foo(_ptr: *mut foo) {}
|
||||
//! # pub unsafe fn destroy_foo(_ptr: *mut foo) {}
|
||||
//! # pub unsafe fn enable_foo(_ptr: *mut foo, _flags: u32) -> i32 { 0 }
|
||||
//! # }
|
||||
//! # // `Error::from_errno` is `pub(crate)` in the `kernel` crate, thus provide a workaround.
|
||||
//! # trait FromErrno {
|
||||
//! # fn from_errno(errno: core::ffi::c_int) -> Error {
|
||||
//! # // Dummy error that can be constructed outside the `kernel` crate.
|
||||
//! # Error::from(core::fmt::Error)
|
||||
//! # }
|
||||
//! # }
|
||||
//! # impl FromErrno for Error {}
|
||||
//! /// # Invariants
|
||||
//! ///
|
||||
//! /// `foo` is always initialized
|
||||
|
@ -206,7 +198,7 @@ pub trait InPlaceInit<T>: Sized {
|
|||
///
|
||||
/// ```rust
|
||||
/// use kernel::error::Error;
|
||||
/// use pin_init::zeroed;
|
||||
/// use pin_init::init_zeroed;
|
||||
/// struct BigBuf {
|
||||
/// big: KBox<[u8; 1024 * 1024 * 1024]>,
|
||||
/// small: [u8; 1024 * 1024],
|
||||
|
@ -215,7 +207,7 @@ pub trait InPlaceInit<T>: Sized {
|
|||
/// impl BigBuf {
|
||||
/// fn new() -> impl Init<Self, Error> {
|
||||
/// try_init!(Self {
|
||||
/// big: KBox::init(zeroed(), GFP_KERNEL)?,
|
||||
/// big: KBox::init(init_zeroed(), GFP_KERNEL)?,
|
||||
/// small: [0; 1024 * 1024],
|
||||
/// }? Error)
|
||||
/// }
|
||||
|
@ -264,7 +256,7 @@ macro_rules! try_init {
|
|||
/// ```rust
|
||||
/// # #![feature(new_uninit)]
|
||||
/// use kernel::error::Error;
|
||||
/// use pin_init::zeroed;
|
||||
/// use pin_init::init_zeroed;
|
||||
/// #[pin_data]
|
||||
/// struct BigBuf {
|
||||
/// big: KBox<[u8; 1024 * 1024 * 1024]>,
|
||||
|
@ -275,7 +267,7 @@ macro_rules! try_init {
|
|||
/// impl BigBuf {
|
||||
/// fn new() -> impl PinInit<Self, Error> {
|
||||
/// try_pin_init!(Self {
|
||||
/// big: KBox::init(zeroed(), GFP_KERNEL)?,
|
||||
/// big: KBox::init(init_zeroed(), GFP_KERNEL)?,
|
||||
/// small: [0; 1024 * 1024],
|
||||
/// ptr: core::ptr::null_mut(),
|
||||
/// }? Error)
|
||||
|
|
|
@ -125,7 +125,7 @@ impl DriverData {
|
|||
fn new() -> impl PinInit<Self, Error> {
|
||||
try_pin_init!(Self {
|
||||
status <- CMutex::new(0),
|
||||
buffer: Box::init(pin_init::zeroed())?,
|
||||
buffer: Box::init(pin_init::init_zeroed())?,
|
||||
}? Error)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@ use pin_init::*;
|
|||
|
||||
// Struct with size over 1GiB
|
||||
#[derive(Debug)]
|
||||
#[allow(dead_code)]
|
||||
pub struct BigStruct {
|
||||
buf: [u8; 1024 * 1024 * 1024],
|
||||
a: u64,
|
||||
|
@ -20,20 +21,23 @@ pub struct ManagedBuf {
|
|||
|
||||
impl ManagedBuf {
|
||||
pub fn new() -> impl Init<Self> {
|
||||
init!(ManagedBuf { buf <- zeroed() })
|
||||
init!(ManagedBuf { buf <- init_zeroed() })
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
// we want to initialize the struct in-place, otherwise we would get a stackoverflow
|
||||
let buf: Box<BigStruct> = Box::init(init!(BigStruct {
|
||||
buf <- zeroed(),
|
||||
a: 7,
|
||||
b: 186,
|
||||
c: 7789,
|
||||
d: 34,
|
||||
managed_buf <- ManagedBuf::new(),
|
||||
}))
|
||||
.unwrap();
|
||||
println!("{}", core::mem::size_of_val(&*buf));
|
||||
#[cfg(any(feature = "std", feature = "alloc"))]
|
||||
{
|
||||
// we want to initialize the struct in-place, otherwise we would get a stackoverflow
|
||||
let buf: Box<BigStruct> = Box::init(init!(BigStruct {
|
||||
buf <- init_zeroed(),
|
||||
a: 7,
|
||||
b: 186,
|
||||
c: 7789,
|
||||
d: 34,
|
||||
managed_buf <- ManagedBuf::new(),
|
||||
}))
|
||||
.unwrap();
|
||||
println!("{}", core::mem::size_of_val(&*buf));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -14,8 +14,9 @@ use core::{
|
|||
|
||||
use pin_init::*;
|
||||
|
||||
#[expect(unused_attributes)]
|
||||
#[allow(unused_attributes)]
|
||||
mod error;
|
||||
#[allow(unused_imports)]
|
||||
use error::Error;
|
||||
|
||||
#[pin_data(PinnedDrop)]
|
||||
|
@ -39,6 +40,7 @@ impl ListHead {
|
|||
}
|
||||
|
||||
#[inline]
|
||||
#[allow(dead_code)]
|
||||
pub fn insert_next(list: &ListHead) -> impl PinInit<Self, Infallible> + '_ {
|
||||
try_pin_init!(&this in Self {
|
||||
prev: list.next.prev().replace(unsafe { Link::new_unchecked(this)}),
|
||||
|
@ -112,6 +114,7 @@ impl Link {
|
|||
}
|
||||
|
||||
#[inline]
|
||||
#[allow(dead_code)]
|
||||
fn prev(&self) -> &Link {
|
||||
unsafe { &(*self.0.get().as_ptr()).prev }
|
||||
}
|
||||
|
@ -137,8 +140,13 @@ impl Link {
|
|||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[cfg(not(any(feature = "std", feature = "alloc")))]
|
||||
fn main() {}
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[cfg_attr(test, test)]
|
||||
#[cfg(any(feature = "std", feature = "alloc"))]
|
||||
fn main() -> Result<(), Error> {
|
||||
let a = Box::pin_init(ListHead::new())?;
|
||||
stack_pin_init!(let b = ListHead::insert_next(&a));
|
||||
|
|
|
@ -12,14 +12,15 @@ use core::{
|
|||
pin::Pin,
|
||||
sync::atomic::{AtomicBool, Ordering},
|
||||
};
|
||||
#[cfg(feature = "std")]
|
||||
use std::{
|
||||
sync::Arc,
|
||||
thread::{self, park, sleep, Builder, Thread},
|
||||
thread::{self, sleep, Builder, Thread},
|
||||
time::Duration,
|
||||
};
|
||||
|
||||
use pin_init::*;
|
||||
#[expect(unused_attributes)]
|
||||
#[allow(unused_attributes)]
|
||||
#[path = "./linked_list.rs"]
|
||||
pub mod linked_list;
|
||||
use linked_list::*;
|
||||
|
@ -36,6 +37,7 @@ impl SpinLock {
|
|||
.compare_exchange(false, true, Ordering::Acquire, Ordering::Relaxed)
|
||||
.is_err()
|
||||
{
|
||||
#[cfg(feature = "std")]
|
||||
while self.inner.load(Ordering::Relaxed) {
|
||||
thread::yield_now();
|
||||
}
|
||||
|
@ -94,7 +96,8 @@ impl<T> CMutex<T> {
|
|||
// println!("wait list length: {}", self.wait_list.size());
|
||||
while self.locked.get() {
|
||||
drop(sguard);
|
||||
park();
|
||||
#[cfg(feature = "std")]
|
||||
thread::park();
|
||||
sguard = self.spin_lock.acquire();
|
||||
}
|
||||
// This does have an effect, as the ListHead inside wait_entry implements Drop!
|
||||
|
@ -131,8 +134,11 @@ impl<T> Drop for CMutexGuard<'_, T> {
|
|||
let sguard = self.mtx.spin_lock.acquire();
|
||||
self.mtx.locked.set(false);
|
||||
if let Some(list_field) = self.mtx.wait_list.next() {
|
||||
let wait_entry = list_field.as_ptr().cast::<WaitEntry>();
|
||||
unsafe { (*wait_entry).thread.unpark() };
|
||||
let _wait_entry = list_field.as_ptr().cast::<WaitEntry>();
|
||||
#[cfg(feature = "std")]
|
||||
unsafe {
|
||||
(*_wait_entry).thread.unpark()
|
||||
};
|
||||
}
|
||||
drop(sguard);
|
||||
}
|
||||
|
@ -159,52 +165,61 @@ impl<T> DerefMut for CMutexGuard<'_, T> {
|
|||
struct WaitEntry {
|
||||
#[pin]
|
||||
wait_list: ListHead,
|
||||
#[cfg(feature = "std")]
|
||||
thread: Thread,
|
||||
}
|
||||
|
||||
impl WaitEntry {
|
||||
#[inline]
|
||||
fn insert_new(list: &ListHead) -> impl PinInit<Self> + '_ {
|
||||
pin_init!(Self {
|
||||
thread: thread::current(),
|
||||
wait_list <- ListHead::insert_prev(list),
|
||||
})
|
||||
#[cfg(feature = "std")]
|
||||
{
|
||||
pin_init!(Self {
|
||||
thread: thread::current(),
|
||||
wait_list <- ListHead::insert_prev(list),
|
||||
})
|
||||
}
|
||||
#[cfg(not(feature = "std"))]
|
||||
{
|
||||
pin_init!(Self {
|
||||
wait_list <- ListHead::insert_prev(list),
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(any(feature = "std", feature = "alloc")))]
|
||||
fn main() {}
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[cfg_attr(test, test)]
|
||||
#[cfg(any(feature = "std", feature = "alloc"))]
|
||||
#[allow(dead_code)]
|
||||
fn main() {
|
||||
let mtx: Pin<Arc<CMutex<usize>>> = Arc::pin_init(CMutex::new(0)).unwrap();
|
||||
let mut handles = vec![];
|
||||
let thread_count = 20;
|
||||
let workload = if cfg!(miri) { 100 } else { 1_000 };
|
||||
for i in 0..thread_count {
|
||||
let mtx = mtx.clone();
|
||||
handles.push(
|
||||
Builder::new()
|
||||
.name(format!("worker #{i}"))
|
||||
.spawn(move || {
|
||||
for _ in 0..workload {
|
||||
*mtx.lock() += 1;
|
||||
}
|
||||
println!("{i} halfway");
|
||||
sleep(Duration::from_millis((i as u64) * 10));
|
||||
for _ in 0..workload {
|
||||
*mtx.lock() += 1;
|
||||
}
|
||||
println!("{i} finished");
|
||||
})
|
||||
.expect("should not fail"),
|
||||
);
|
||||
#[cfg(feature = "std")]
|
||||
{
|
||||
let mtx: Pin<Arc<CMutex<usize>>> = Arc::pin_init(CMutex::new(0)).unwrap();
|
||||
let mut handles = vec![];
|
||||
let thread_count = 20;
|
||||
let workload = if cfg!(miri) { 100 } else { 1_000 };
|
||||
for i in 0..thread_count {
|
||||
let mtx = mtx.clone();
|
||||
handles.push(
|
||||
Builder::new()
|
||||
.name(format!("worker #{i}"))
|
||||
.spawn(move || {
|
||||
for _ in 0..workload {
|
||||
*mtx.lock() += 1;
|
||||
}
|
||||
println!("{i} halfway");
|
||||
sleep(Duration::from_millis((i as u64) * 10));
|
||||
for _ in 0..workload {
|
||||
*mtx.lock() += 1;
|
||||
}
|
||||
println!("{i} finished");
|
||||
})
|
||||
.expect("should not fail"),
|
||||
);
|
||||
}
|
||||
for h in handles {
|
||||
h.join().expect("thread panicked");
|
||||
}
|
||||
println!("{:?}", &*mtx.lock());
|
||||
assert_eq!(*mtx.lock(), workload * thread_count * 2);
|
||||
}
|
||||
for h in handles {
|
||||
h.join().expect("thread panicked");
|
||||
}
|
||||
println!("{:?}", &*mtx.lock());
|
||||
assert_eq!(*mtx.lock(), workload * thread_count * 2);
|
||||
}
|
||||
|
|
|
@ -44,6 +44,7 @@ mod pthread_mtx {
|
|||
pub enum Error {
|
||||
#[allow(dead_code)]
|
||||
IO(std::io::Error),
|
||||
#[allow(dead_code)]
|
||||
Alloc,
|
||||
}
|
||||
|
||||
|
@ -61,6 +62,7 @@ mod pthread_mtx {
|
|||
}
|
||||
|
||||
impl<T> PThreadMutex<T> {
|
||||
#[allow(dead_code)]
|
||||
pub fn new(data: T) -> impl PinInit<Self, Error> {
|
||||
fn init_raw() -> impl PinInit<UnsafeCell<libc::pthread_mutex_t>, Error> {
|
||||
let init = |slot: *mut UnsafeCell<libc::pthread_mutex_t>| {
|
||||
|
@ -103,6 +105,7 @@ mod pthread_mtx {
|
|||
}? Error)
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn lock(&self) -> PThreadMutexGuard<'_, T> {
|
||||
// SAFETY: raw is always initialized
|
||||
unsafe { libc::pthread_mutex_lock(self.raw.get()) };
|
||||
|
@ -137,6 +140,7 @@ mod pthread_mtx {
|
|||
}
|
||||
|
||||
#[cfg_attr(test, test)]
|
||||
#[cfg_attr(all(test, miri), ignore)]
|
||||
fn main() {
|
||||
#[cfg(all(any(feature = "std", feature = "alloc"), not(windows)))]
|
||||
{
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
#![allow(clippy::undocumented_unsafe_blocks)]
|
||||
#![cfg_attr(feature = "alloc", feature(allocator_api))]
|
||||
#![cfg_attr(not(RUSTC_LINT_REASONS_IS_STABLE), feature(lint_reasons))]
|
||||
#![allow(unused_imports)]
|
||||
|
||||
use core::{
|
||||
cell::{Cell, UnsafeCell},
|
||||
|
@ -12,12 +13,13 @@ use core::{
|
|||
time::Duration,
|
||||
};
|
||||
use pin_init::*;
|
||||
#[cfg(feature = "std")]
|
||||
use std::{
|
||||
sync::Arc,
|
||||
thread::{sleep, Builder},
|
||||
};
|
||||
|
||||
#[expect(unused_attributes)]
|
||||
#[allow(unused_attributes)]
|
||||
mod mutex;
|
||||
use mutex::*;
|
||||
|
||||
|
@ -82,42 +84,41 @@ unsafe impl PinInit<CMutex<usize>> for CountInit {
|
|||
|
||||
pub static COUNT: StaticInit<CMutex<usize>, CountInit> = StaticInit::new(CountInit);
|
||||
|
||||
#[cfg(not(any(feature = "std", feature = "alloc")))]
|
||||
fn main() {}
|
||||
|
||||
#[cfg(any(feature = "std", feature = "alloc"))]
|
||||
fn main() {
|
||||
let mtx: Pin<Arc<CMutex<usize>>> = Arc::pin_init(CMutex::new(0)).unwrap();
|
||||
let mut handles = vec![];
|
||||
let thread_count = 20;
|
||||
let workload = 1_000;
|
||||
for i in 0..thread_count {
|
||||
let mtx = mtx.clone();
|
||||
handles.push(
|
||||
Builder::new()
|
||||
.name(format!("worker #{i}"))
|
||||
.spawn(move || {
|
||||
for _ in 0..workload {
|
||||
*COUNT.lock() += 1;
|
||||
std::thread::sleep(std::time::Duration::from_millis(10));
|
||||
*mtx.lock() += 1;
|
||||
std::thread::sleep(std::time::Duration::from_millis(10));
|
||||
*COUNT.lock() += 1;
|
||||
}
|
||||
println!("{i} halfway");
|
||||
sleep(Duration::from_millis((i as u64) * 10));
|
||||
for _ in 0..workload {
|
||||
std::thread::sleep(std::time::Duration::from_millis(10));
|
||||
*mtx.lock() += 1;
|
||||
}
|
||||
println!("{i} finished");
|
||||
})
|
||||
.expect("should not fail"),
|
||||
);
|
||||
#[cfg(feature = "std")]
|
||||
{
|
||||
let mtx: Pin<Arc<CMutex<usize>>> = Arc::pin_init(CMutex::new(0)).unwrap();
|
||||
let mut handles = vec![];
|
||||
let thread_count = 20;
|
||||
let workload = 1_000;
|
||||
for i in 0..thread_count {
|
||||
let mtx = mtx.clone();
|
||||
handles.push(
|
||||
Builder::new()
|
||||
.name(format!("worker #{i}"))
|
||||
.spawn(move || {
|
||||
for _ in 0..workload {
|
||||
*COUNT.lock() += 1;
|
||||
std::thread::sleep(std::time::Duration::from_millis(10));
|
||||
*mtx.lock() += 1;
|
||||
std::thread::sleep(std::time::Duration::from_millis(10));
|
||||
*COUNT.lock() += 1;
|
||||
}
|
||||
println!("{i} halfway");
|
||||
sleep(Duration::from_millis((i as u64) * 10));
|
||||
for _ in 0..workload {
|
||||
std::thread::sleep(std::time::Duration::from_millis(10));
|
||||
*mtx.lock() += 1;
|
||||
}
|
||||
println!("{i} finished");
|
||||
})
|
||||
.expect("should not fail"),
|
||||
);
|
||||
}
|
||||
for h in handles {
|
||||
h.join().expect("thread panicked");
|
||||
}
|
||||
println!("{:?}, {:?}", &*mtx.lock(), &*COUNT.lock());
|
||||
assert_eq!(*mtx.lock(), workload * thread_count * 2);
|
||||
}
|
||||
for h in handles {
|
||||
h.join().expect("thread panicked");
|
||||
}
|
||||
println!("{:?}, {:?}", &*mtx.lock(), &*COUNT.lock());
|
||||
assert_eq!(*mtx.lock(), workload * thread_count * 2);
|
||||
}
|
||||
|
|
|
@ -188,6 +188,7 @@ impl<T> StackInit<T> {
|
|||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(feature = "std")]
|
||||
fn stack_init_reuse() {
|
||||
use ::std::{borrow::ToOwned, println, string::String};
|
||||
use core::pin::pin;
|
||||
|
|
|
@ -148,7 +148,7 @@
|
|||
//! fn new() -> impl PinInit<Self, Error> {
|
||||
//! try_pin_init!(Self {
|
||||
//! status <- CMutex::new(0),
|
||||
//! buffer: Box::init(pin_init::zeroed())?,
|
||||
//! buffer: Box::init(pin_init::init_zeroed())?,
|
||||
//! }? Error)
|
||||
//! }
|
||||
//! }
|
||||
|
@ -742,7 +742,7 @@ macro_rules! stack_try_pin_init {
|
|||
/// - Fields that you want to initialize in-place have to use `<-` instead of `:`.
|
||||
/// - In front of the initializer you can write `&this in` to have access to a [`NonNull<Self>`]
|
||||
/// pointer named `this` inside of the initializer.
|
||||
/// - Using struct update syntax one can place `..Zeroable::zeroed()` at the very end of the
|
||||
/// - Using struct update syntax one can place `..Zeroable::init_zeroed()` at the very end of the
|
||||
/// struct, this initializes every field with 0 and then runs all initializers specified in the
|
||||
/// body. This can only be done if [`Zeroable`] is implemented for the struct.
|
||||
///
|
||||
|
@ -769,7 +769,7 @@ macro_rules! stack_try_pin_init {
|
|||
/// });
|
||||
/// let init = pin_init!(Buf {
|
||||
/// buf: [1; 64],
|
||||
/// ..Zeroable::zeroed()
|
||||
/// ..Zeroable::init_zeroed()
|
||||
/// });
|
||||
/// ```
|
||||
///
|
||||
|
@ -805,7 +805,7 @@ macro_rules! pin_init {
|
|||
/// ```rust
|
||||
/// # #![feature(allocator_api)]
|
||||
/// # #[path = "../examples/error.rs"] mod error; use error::Error;
|
||||
/// use pin_init::{pin_data, try_pin_init, PinInit, InPlaceInit, zeroed};
|
||||
/// use pin_init::{pin_data, try_pin_init, PinInit, InPlaceInit, init_zeroed};
|
||||
///
|
||||
/// #[pin_data]
|
||||
/// struct BigBuf {
|
||||
|
@ -817,7 +817,7 @@ macro_rules! pin_init {
|
|||
/// impl BigBuf {
|
||||
/// fn new() -> impl PinInit<Self, Error> {
|
||||
/// try_pin_init!(Self {
|
||||
/// big: Box::init(zeroed())?,
|
||||
/// big: Box::init(init_zeroed())?,
|
||||
/// small: [0; 1024 * 1024],
|
||||
/// ptr: core::ptr::null_mut(),
|
||||
/// }? Error)
|
||||
|
@ -866,7 +866,7 @@ macro_rules! try_pin_init {
|
|||
/// # #[path = "../examples/error.rs"] mod error; use error::Error;
|
||||
/// # #[path = "../examples/mutex.rs"] mod mutex; use mutex::*;
|
||||
/// # use pin_init::InPlaceInit;
|
||||
/// use pin_init::{init, Init, zeroed};
|
||||
/// use pin_init::{init, Init, init_zeroed};
|
||||
///
|
||||
/// struct BigBuf {
|
||||
/// small: [u8; 1024 * 1024],
|
||||
|
@ -875,7 +875,7 @@ macro_rules! try_pin_init {
|
|||
/// impl BigBuf {
|
||||
/// fn new() -> impl Init<Self> {
|
||||
/// init!(Self {
|
||||
/// small <- zeroed(),
|
||||
/// small <- init_zeroed(),
|
||||
/// })
|
||||
/// }
|
||||
/// }
|
||||
|
@ -913,7 +913,7 @@ macro_rules! init {
|
|||
/// # #![feature(allocator_api)]
|
||||
/// # use core::alloc::AllocError;
|
||||
/// # use pin_init::InPlaceInit;
|
||||
/// use pin_init::{try_init, Init, zeroed};
|
||||
/// use pin_init::{try_init, Init, init_zeroed};
|
||||
///
|
||||
/// struct BigBuf {
|
||||
/// big: Box<[u8; 1024 * 1024 * 1024]>,
|
||||
|
@ -923,7 +923,7 @@ macro_rules! init {
|
|||
/// impl BigBuf {
|
||||
/// fn new() -> impl Init<Self, AllocError> {
|
||||
/// try_init!(Self {
|
||||
/// big: Box::init(zeroed())?,
|
||||
/// big: Box::init(init_zeroed())?,
|
||||
/// small: [0; 1024 * 1024],
|
||||
/// }? AllocError)
|
||||
/// }
|
||||
|
@ -1170,7 +1170,7 @@ pub unsafe trait Init<T: ?Sized, E = Infallible>: PinInit<T, E> {
|
|||
///
|
||||
/// ```rust
|
||||
/// # #![expect(clippy::disallowed_names)]
|
||||
/// use pin_init::{init, zeroed, Init};
|
||||
/// use pin_init::{init, init_zeroed, Init};
|
||||
///
|
||||
/// struct Foo {
|
||||
/// buf: [u8; 1_000_000],
|
||||
|
@ -1183,7 +1183,7 @@ pub unsafe trait Init<T: ?Sized, E = Infallible>: PinInit<T, E> {
|
|||
/// }
|
||||
///
|
||||
/// let foo = init!(Foo {
|
||||
/// buf <- zeroed()
|
||||
/// buf <- init_zeroed()
|
||||
/// }).chain(|foo| {
|
||||
/// foo.setup();
|
||||
/// Ok(())
|
||||
|
@ -1390,20 +1390,44 @@ where
|
|||
unsafe { pin_init_from_closure(init) }
|
||||
}
|
||||
|
||||
// SAFETY: Every type can be initialized by-value.
|
||||
unsafe impl<T, E> Init<T, E> for T {
|
||||
unsafe fn __init(self, slot: *mut T) -> Result<(), E> {
|
||||
// SAFETY: TODO.
|
||||
// SAFETY: the `__init` function always returns `Ok(())` and initializes every field of `slot`.
|
||||
unsafe impl<T> Init<T> for T {
|
||||
unsafe fn __init(self, slot: *mut T) -> Result<(), Infallible> {
|
||||
// SAFETY: `slot` is valid for writes by the safety requirements of this function.
|
||||
unsafe { slot.write(self) };
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
// SAFETY: Every type can be initialized by-value. `__pinned_init` calls `__init`.
|
||||
unsafe impl<T, E> PinInit<T, E> for T {
|
||||
// SAFETY: the `__pinned_init` function always returns `Ok(())` and initializes every field of
|
||||
// `slot`. Additionally, all pinning invariants of `T` are upheld.
|
||||
unsafe impl<T> PinInit<T> for T {
|
||||
unsafe fn __pinned_init(self, slot: *mut T) -> Result<(), Infallible> {
|
||||
// SAFETY: `slot` is valid for writes by the safety requirements of this function.
|
||||
unsafe { slot.write(self) };
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
// SAFETY: when the `__init` function returns with
|
||||
// - `Ok(())`, `slot` was initialized and all pinned invariants of `T` are upheld.
|
||||
// - `Err(err)`, slot was not written to.
|
||||
unsafe impl<T, E> Init<T, E> for Result<T, E> {
|
||||
unsafe fn __init(self, slot: *mut T) -> Result<(), E> {
|
||||
// SAFETY: `slot` is valid for writes by the safety requirements of this function.
|
||||
unsafe { slot.write(self?) };
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
// SAFETY: when the `__pinned_init` function returns with
|
||||
// - `Ok(())`, `slot` was initialized and all pinned invariants of `T` are upheld.
|
||||
// - `Err(err)`, slot was not written to.
|
||||
unsafe impl<T, E> PinInit<T, E> for Result<T, E> {
|
||||
unsafe fn __pinned_init(self, slot: *mut T) -> Result<(), E> {
|
||||
// SAFETY: TODO.
|
||||
unsafe { self.__init(slot) }
|
||||
// SAFETY: `slot` is valid for writes by the safety requirements of this function.
|
||||
unsafe { slot.write(self?) };
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1471,7 +1495,45 @@ pub unsafe trait PinnedDrop: __internal::HasPinData {
|
|||
/// ```rust,ignore
|
||||
/// let val: Self = unsafe { core::mem::zeroed() };
|
||||
/// ```
|
||||
pub unsafe trait Zeroable {}
|
||||
pub unsafe trait Zeroable {
|
||||
/// Create a new zeroed `Self`.
|
||||
///
|
||||
/// The returned initializer will write `0x00` to every byte of the given `slot`.
|
||||
#[inline]
|
||||
fn init_zeroed() -> impl Init<Self>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
init_zeroed()
|
||||
}
|
||||
|
||||
/// Create a `Self` consisting of all zeroes.
|
||||
///
|
||||
/// Whenever a type implements [`Zeroable`], this function should be preferred over
|
||||
/// [`core::mem::zeroed()`] or using `MaybeUninit<T>::zeroed().assume_init()`.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use pin_init::{Zeroable, zeroed};
|
||||
///
|
||||
/// #[derive(Zeroable)]
|
||||
/// struct Point {
|
||||
/// x: u32,
|
||||
/// y: u32,
|
||||
/// }
|
||||
///
|
||||
/// let point: Point = zeroed();
|
||||
/// assert_eq!(point.x, 0);
|
||||
/// assert_eq!(point.y, 0);
|
||||
/// ```
|
||||
fn zeroed() -> Self
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
zeroed()
|
||||
}
|
||||
}
|
||||
|
||||
/// Marker trait for types that allow `Option<Self>` to be set to all zeroes in order to write
|
||||
/// `None` to that location.
|
||||
|
@ -1484,11 +1546,21 @@ pub unsafe trait ZeroableOption {}
|
|||
// SAFETY: by the safety requirement of `ZeroableOption`, this is valid.
|
||||
unsafe impl<T: ZeroableOption> Zeroable for Option<T> {}
|
||||
|
||||
/// Create a new zeroed T.
|
||||
// SAFETY: `Option<&T>` is part of the option layout optimization guarantee:
|
||||
// <https://doc.rust-lang.org/stable/std/option/index.html#representation>.
|
||||
unsafe impl<T> ZeroableOption for &T {}
|
||||
// SAFETY: `Option<&mut T>` is part of the option layout optimization guarantee:
|
||||
// <https://doc.rust-lang.org/stable/std/option/index.html#representation>.
|
||||
unsafe impl<T> ZeroableOption for &mut T {}
|
||||
// SAFETY: `Option<NonNull<T>>` is part of the option layout optimization guarantee:
|
||||
// <https://doc.rust-lang.org/stable/std/option/index.html#representation>.
|
||||
unsafe impl<T> ZeroableOption for NonNull<T> {}
|
||||
|
||||
/// Create an initializer for a zeroed `T`.
|
||||
///
|
||||
/// The returned initializer will write `0x00` to every byte of the given `slot`.
|
||||
#[inline]
|
||||
pub fn zeroed<T: Zeroable>() -> impl Init<T> {
|
||||
pub fn init_zeroed<T: Zeroable>() -> impl Init<T> {
|
||||
// SAFETY: Because `T: Zeroable`, all bytes zero is a valid bit pattern for `T`
|
||||
// and because we write all zeroes, the memory is initialized.
|
||||
unsafe {
|
||||
|
@ -1499,6 +1571,31 @@ pub fn zeroed<T: Zeroable>() -> impl Init<T> {
|
|||
}
|
||||
}
|
||||
|
||||
/// Create a `T` consisting of all zeroes.
|
||||
///
|
||||
/// Whenever a type implements [`Zeroable`], this function should be preferred over
|
||||
/// [`core::mem::zeroed()`] or using `MaybeUninit<T>::zeroed().assume_init()`.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use pin_init::{Zeroable, zeroed};
|
||||
///
|
||||
/// #[derive(Zeroable)]
|
||||
/// struct Point {
|
||||
/// x: u32,
|
||||
/// y: u32,
|
||||
/// }
|
||||
///
|
||||
/// let point: Point = zeroed();
|
||||
/// assert_eq!(point.x, 0);
|
||||
/// assert_eq!(point.y, 0);
|
||||
/// ```
|
||||
pub const fn zeroed<T: Zeroable>() -> T {
|
||||
// SAFETY:By the type invariants of `Zeroable`, all zeroes is a valid bit pattern for `T`.
|
||||
unsafe { core::mem::zeroed() }
|
||||
}
|
||||
|
||||
macro_rules! impl_zeroable {
|
||||
($($({$($generics:tt)*})? $t:ty, )*) => {
|
||||
// SAFETY: Safety comments written in the macro invocation.
|
||||
|
@ -1536,7 +1633,6 @@ impl_zeroable! {
|
|||
Option<NonZeroU128>, Option<NonZeroUsize>,
|
||||
Option<NonZeroI8>, Option<NonZeroI16>, Option<NonZeroI32>, Option<NonZeroI64>,
|
||||
Option<NonZeroI128>, Option<NonZeroIsize>,
|
||||
{<T>} Option<NonNull<T>>,
|
||||
|
||||
// SAFETY: `null` pointer is valid.
|
||||
//
|
||||
|
@ -1566,6 +1662,22 @@ macro_rules! impl_tuple_zeroable {
|
|||
|
||||
impl_tuple_zeroable!(A, B, C, D, E, F, G, H, I, J);
|
||||
|
||||
macro_rules! impl_fn_zeroable_option {
|
||||
([$($abi:literal),* $(,)?] $args:tt) => {
|
||||
$(impl_fn_zeroable_option!({extern $abi} $args);)*
|
||||
$(impl_fn_zeroable_option!({unsafe extern $abi} $args);)*
|
||||
};
|
||||
({$($prefix:tt)*} {$(,)?}) => {};
|
||||
({$($prefix:tt)*} {$ret:ident, $($rest:ident),* $(,)?}) => {
|
||||
// SAFETY: function pointers are part of the option layout optimization:
|
||||
// <https://doc.rust-lang.org/stable/std/option/index.html#representation>.
|
||||
unsafe impl<$ret, $($rest),*> ZeroableOption for $($prefix)* fn($($rest),*) -> $ret {}
|
||||
impl_fn_zeroable_option!({$($prefix)*} {$($rest),*,});
|
||||
};
|
||||
}
|
||||
|
||||
impl_fn_zeroable_option!(["Rust", "C"] { A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U });
|
||||
|
||||
/// This trait allows creating an instance of `Self` which contains exactly one
|
||||
/// [structurally pinned value](https://doc.rust-lang.org/std/pin/index.html#projections-and-structural-pinning).
|
||||
///
|
||||
|
|
|
@ -1030,7 +1030,7 @@ macro_rules! __pin_data {
|
|||
///
|
||||
/// This macro has multiple internal call configurations, these are always the very first ident:
|
||||
/// - nothing: this is the base case and called by the `{try_}{pin_}init!` macros.
|
||||
/// - `with_update_parsed`: when the `..Zeroable::zeroed()` syntax has been handled.
|
||||
/// - `with_update_parsed`: when the `..Zeroable::init_zeroed()` syntax has been handled.
|
||||
/// - `init_slot`: recursively creates the code that initializes all fields in `slot`.
|
||||
/// - `make_initializer`: recursively create the struct initializer that guarantees that every
|
||||
/// field has been initialized exactly once.
|
||||
|
@ -1059,7 +1059,7 @@ macro_rules! __init_internal {
|
|||
@data($data, $($use_data)?),
|
||||
@has_data($has_data, $get_data),
|
||||
@construct_closure($construct_closure),
|
||||
@zeroed(), // Nothing means default behavior.
|
||||
@init_zeroed(), // Nothing means default behavior.
|
||||
)
|
||||
};
|
||||
(
|
||||
|
@ -1074,7 +1074,7 @@ macro_rules! __init_internal {
|
|||
@has_data($has_data:ident, $get_data:ident),
|
||||
// `pin_init_from_closure` or `init_from_closure`.
|
||||
@construct_closure($construct_closure:ident),
|
||||
@munch_fields(..Zeroable::zeroed()),
|
||||
@munch_fields(..Zeroable::init_zeroed()),
|
||||
) => {
|
||||
$crate::__init_internal!(with_update_parsed:
|
||||
@this($($this)?),
|
||||
|
@ -1084,7 +1084,7 @@ macro_rules! __init_internal {
|
|||
@data($data, $($use_data)?),
|
||||
@has_data($has_data, $get_data),
|
||||
@construct_closure($construct_closure),
|
||||
@zeroed(()), // `()` means zero all fields not mentioned.
|
||||
@init_zeroed(()), // `()` means zero all fields not mentioned.
|
||||
)
|
||||
};
|
||||
(
|
||||
|
@ -1124,7 +1124,7 @@ macro_rules! __init_internal {
|
|||
@has_data($has_data:ident, $get_data:ident),
|
||||
// `pin_init_from_closure` or `init_from_closure`.
|
||||
@construct_closure($construct_closure:ident),
|
||||
@zeroed($($init_zeroed:expr)?),
|
||||
@init_zeroed($($init_zeroed:expr)?),
|
||||
) => {{
|
||||
// We do not want to allow arbitrary returns, so we declare this type as the `Ok` return
|
||||
// type and shadow it later when we insert the arbitrary user code. That way there will be
|
||||
|
@ -1196,7 +1196,7 @@ macro_rules! __init_internal {
|
|||
@data($data:ident),
|
||||
@slot($slot:ident),
|
||||
@guards($($guards:ident,)*),
|
||||
@munch_fields($(..Zeroable::zeroed())? $(,)?),
|
||||
@munch_fields($(..Zeroable::init_zeroed())? $(,)?),
|
||||
) => {
|
||||
// Endpoint of munching, no fields are left. If execution reaches this point, all fields
|
||||
// have been initialized. Therefore we can now dismiss the guards by forgetting them.
|
||||
|
@ -1300,11 +1300,11 @@ macro_rules! __init_internal {
|
|||
(make_initializer:
|
||||
@slot($slot:ident),
|
||||
@type_name($t:path),
|
||||
@munch_fields(..Zeroable::zeroed() $(,)?),
|
||||
@munch_fields(..Zeroable::init_zeroed() $(,)?),
|
||||
@acc($($acc:tt)*),
|
||||
) => {
|
||||
// Endpoint, nothing more to munch, create the initializer. Since the users specified
|
||||
// `..Zeroable::zeroed()`, the slot will already have been zeroed and all field that have
|
||||
// `..Zeroable::init_zeroed()`, the slot will already have been zeroed and all field that have
|
||||
// not been overwritten are thus zero and initialized. We still check that all fields are
|
||||
// actually accessible by using the struct update syntax ourselves.
|
||||
// We are inside of a closure that is never executed and thus we can abuse `slot` to
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue