Skip to content

Commit a11eac9

Browse files
committed
internal: project slots instead not references
By projecting slots, the `pin_init!` and `init!` code path can be more unified. This also reduces the amount of macro-generated code and shifts them to the shared infrastructure. Signed-off-by: Gary Guo <gary@garyguo.net>
1 parent d59c2fb commit a11eac9

13 files changed

Lines changed: 236 additions & 286 deletions

internal/src/init.rs

Lines changed: 33 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
// SPDX-License-Identifier: Apache-2.0 OR MIT
22

33
use proc_macro2::{Span, TokenStream};
4-
use quote::{format_ident, quote, quote_spanned};
4+
use quote::{format_ident, quote};
55
use syn::{
66
braced,
77
parse::{End, Parse},
@@ -229,102 +229,57 @@ fn init_fields(
229229
}
230230
};
231231

232-
let init = match kind {
233-
InitializerKind::Value { ident, value } => {
234-
let mut value_ident = ident.clone();
235-
let value_prep = value.as_ref().map(|value| &value.1).map(|value| {
236-
// Setting the span of `value_ident` to `value`'s span improves error messages
237-
// when the type of `value` is wrong.
238-
value_ident.set_span(value.span());
239-
quote!(let #value_ident = #value;)
240-
});
241-
// Again span for better diagnostics
242-
let write = quote_spanned!(ident.span()=> ::core::ptr::write);
243-
quote! {
244-
#(#attrs)*
245-
{
246-
#value_prep
247-
// SAFETY: TODO
248-
unsafe { #write(&raw mut (*#slot).#ident, #value_ident) };
249-
}
250-
}
251-
}
252-
InitializerKind::Init { ident, value, .. } => {
253-
// Again span for better diagnostics
254-
let init = format_ident!("init", span = value.span());
255-
let value_init = if pinned {
256-
quote! {
257-
// SAFETY:
258-
// - `slot` is valid, because we are inside of an initializer closure, we
259-
// return when an error/panic occurs.
260-
// - We also use `#data` to require the correct trait (`Init` or `PinInit`)
261-
// for `#ident`.
262-
unsafe { #data.#ident(&raw mut (*#slot).#ident, #init)? };
263-
}
264-
} else {
265-
quote! {
266-
// SAFETY: `slot` is valid, because we are inside of an initializer
267-
// closure, we return when an error/panic occurs.
268-
unsafe {
269-
::pin_init::Init::__init(
270-
#init,
271-
&raw mut (*#slot).#ident,
272-
)?
273-
};
274-
}
275-
};
276-
quote! {
277-
#(#attrs)*
278-
{
279-
let #init = #value;
280-
#value_init
281-
}
282-
}
283-
}
284-
InitializerKind::Code { .. } => unreachable!(),
285-
};
286-
287-
// `mixed_site` ensures that the guard is not accessible to the user-controlled code.
288-
let guard = format_ident!("__{ident}_guard", span = Span::mixed_site());
289-
290-
let guard_creation = if pinned {
291-
let project_ident = format_ident!("__project_{ident}");
232+
let slot = if pinned {
292233
quote! {
293234
// SAFETY:
294235
// - `&raw mut (*slot).#ident` points to the `#ident` field of `slot`.
295236
// - `&raw mut (*slot).#ident` is valid.
296237
// - `make_field_check` checks that `&raw mut (*slot).#ident` is properly aligned.
297-
// - `(*slot).#ident` has been initialized above.
298-
// - We only need the ownership to the pointee back when initialization has
299-
// succeeded, where we `forget` the guard.
300-
unsafe { #data.#project_ident(&raw mut (*slot).#ident) }
238+
// - `make_field_check` prevents `#ident` from being used twice, therefore
239+
// `(*slot).#ident` is exclusively accessed and has not been initialized.
240+
(unsafe { #data.#ident(&raw mut (*#slot).#ident) })
301241
}
302242
} else {
303243
quote! {
244+
// For `init!()` macro, everything is unpinned.
304245
// SAFETY:
305246
// - `&raw mut (*slot).#ident` is valid.
306247
// - `make_field_check` checks that `&raw mut (*slot).#ident` is properly aligned.
307-
// - `(*slot).#ident` has been initialized above.
308-
// - We only need the ownership to the pointee back when initialization has
309-
// succeeded, where we `forget` the guard.
310-
unsafe {
311-
::pin_init::__internal::DropGuard::<::pin_init::__internal::Unpinned, _>::new(
312-
&raw mut (*slot).#ident
313-
)
248+
// - `make_field_check` prevents `#ident` from being used twice, therefore
249+
// `(*slot).#ident` is exclusively accessed and has not been initialized.
250+
(unsafe { ::pin_init::__internal::Slot::<::pin_init::__internal::Unpinned, _>::new(&raw mut (*#slot).#ident) })
251+
}
252+
};
253+
254+
// `mixed_site` ensures that the guard is not accessible to the user-controlled code.
255+
let guard = format_ident!("__{ident}_guard", span = Span::mixed_site());
256+
257+
let init = match kind {
258+
InitializerKind::Value { ident, value } => {
259+
let value = value
260+
.as_ref()
261+
.map(|(_, value)| quote!(#value))
262+
.unwrap_or_else(|| quote!(#ident));
263+
264+
quote! {
265+
#(#attrs)*
266+
let mut #guard = #slot.write(#value);
267+
314268
}
315269
}
270+
InitializerKind::Init { value, .. } => {
271+
quote! {
272+
#(#attrs)*
273+
let mut #guard = #slot.init(#value)?;
274+
}
275+
}
276+
InitializerKind::Code { .. } => unreachable!(),
316277
};
317278

318279
res.extend(quote! {
319280
#init
320281

321282
#(#cfgs)*
322-
let mut #guard = #guard_creation;
323-
324-
#(#cfgs)*
325-
// NOTE: The reference is derived from the guard so that it only lives as long as the
326-
// guard does and cannot escape the scope. If it's created via `&mut (*#slot).#ident`
327-
// like the unaligned field guard, it will become effectively `'static`.
328283
#[allow(unused_variables)]
329284
let #ident = #guard.let_binding();
330285
});

internal/src/pin_data.rs

Lines changed: 10 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -341,10 +341,9 @@ fn generate_the_pin_data(
341341
let (impl_generics, ty_generics, whr) = generics.split_for_impl();
342342

343343
// For every field, we create an initializing projection function according to its projection
344-
// type. If a field is structurally pinned, then it must be initialized via `PinInit`, if it is
345-
// not structurally pinned, then it can be initialized via `Init`.
346-
//
347-
// The functions are `unsafe` to prevent accidentally calling them.
344+
// type. If a field is structurally pinned, we create a `Slot` with `Pinned` which must be
345+
// initialized via `PinInit`; if it is not structurally pinned, then we create a `Slot` with
346+
// `Unpinned` which allows initialization via `Init`.
348347
fn handle_field(
349348
Field {
350349
vis,
@@ -358,51 +357,25 @@ fn generate_the_pin_data(
358357
let ident = ident
359358
.as_ref()
360359
.expect("only structs with named fields are supported");
361-
let project_ident = format_ident!("__project_{ident}");
362-
let (init_ty, init_fn, pin_marker, pin_safety) = if pinned {
363-
(
364-
quote!(PinInit),
365-
quote!(__pinned_init),
366-
quote!(Pinned),
367-
quote!(
368-
/// - `slot` will not move until it is dropped, i.e. it will be pinned.
369-
),
370-
)
360+
let pin_marker = if pinned {
361+
quote!(Pinned)
371362
} else {
372-
(quote!(Init), quote!(__init), quote!(Unpinned), quote!())
363+
quote!(Unpinned)
373364
};
374365
quote! {
375-
/// # Safety
376-
///
377-
/// - `slot` is a valid pointer to uninitialized memory.
378-
/// - the caller does not touch `slot` when `Err` is returned, they are only permitted
379-
/// to deallocate.
380-
#pin_safety
381-
#(#attrs)*
382-
#vis unsafe fn #ident<E>(
383-
self,
384-
slot: *mut #ty,
385-
init: impl ::pin_init::#init_ty<#ty, E>,
386-
) -> ::core::result::Result<(), E> {
387-
// SAFETY: this function has the same safety requirements as the __init function
388-
// called below.
389-
unsafe { ::pin_init::#init_ty::#init_fn(init, slot) }
390-
}
391-
392366
/// # Safety
393367
///
394368
/// - `slot` points to a `#ident` field of a pinned struct that this `__ThePinData` describes.
395-
/// - `slot` is valid and properly aligned.
396-
/// - `*slot` is initialized, and the ownership is transferred to the returned guard.
369+
/// - `slot` is a valid, properly aligned and points to uninitialized and exclusively memory.
397370
#(#attrs)*
398-
#vis unsafe fn #project_ident(
371+
#vis unsafe fn #ident(
399372
self,
400373
slot: *mut #ty,
401-
) -> ::pin_init::__internal::DropGuard<::pin_init::__internal::#pin_marker, #ty> {
374+
) -> ::pin_init::__internal::Slot<::pin_init::__internal::#pin_marker, #ty> {
402375
// SAFETY:
403376
// - If `#pin_marker` is `Pinned`, the corresponding field is structurally pinned.
404377
// - Other safety requirements follows the safety requirement.
405-
unsafe { ::pin_init::__internal::DropGuard::new(slot) }
378+
unsafe { ::pin_init::__internal::Slot::new(slot) }
406379
}
407380
}
408381
}

src/__internal.rs

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -257,6 +257,82 @@ fn stack_init_reuse() {
257257
pub struct Pinned;
258258
pub struct Unpinned;
259259

260+
/// Represent an uninitialized field.
261+
///
262+
/// # Invariants
263+
///
264+
/// - `ptr` is valid, properly aligned and points to uninitialized and exclusively accessed memory.
265+
/// - If `P` is `Pinned`, then `ptr` is structurally pinned.
266+
pub struct Slot<P, T: ?Sized> {
267+
ptr: *mut T,
268+
_phantom: PhantomData<P>,
269+
}
270+
271+
impl<P, T: ?Sized> Slot<P, T> {
272+
/// # Safety
273+
///
274+
/// - `ptr` is valid, properly aligned and points to uninitialized and exclusively accessed memory.
275+
/// - If `P` is `Pinned`, then `ptr` is structurally pinned.
276+
#[inline]
277+
pub unsafe fn new(ptr: *mut T) -> Self {
278+
// INVARIANT: Per safety requirement.
279+
Self {
280+
ptr,
281+
_phantom: PhantomData,
282+
}
283+
}
284+
285+
/// Initialize the field by value.
286+
#[inline]
287+
pub fn write(self, value: T) -> DropGuard<P, T>
288+
where
289+
T: Sized,
290+
{
291+
// SAFETY: `self.ptr` is a valid and aligned pointer for write.
292+
unsafe { self.ptr.write(value) }
293+
// SAFETY:
294+
// - `self.ptr` is valid and properly aligned per type invariant.
295+
// - `*self.ptr` is initialized above and the ownership is transferred to the guard.
296+
// - If `P` is `Pinned`, `self.ptr` is pinned.
297+
unsafe { DropGuard::new(self.ptr) }
298+
}
299+
}
300+
301+
impl<T: ?Sized> Slot<Unpinned, T> {
302+
/// Initialize the field.
303+
#[inline]
304+
pub fn init<E>(self, init: impl Init<T, E>) -> Result<DropGuard<Unpinned, T>, E> {
305+
// SAFETY:
306+
// - `self.ptr` is valid and properly aligned.
307+
// - when `Err` is returned, we also propagate the error without touching `slot`;
308+
// also `self` is consumed so it cannot be touched further.
309+
unsafe { init.__init(self.ptr)? };
310+
311+
// SAFETY:
312+
// - `self.ptr` is valid and properly aligned per type invariant.
313+
// - `*self.ptr` is initialized above and the ownership is transferred to the guard.
314+
Ok(unsafe { DropGuard::new(self.ptr) })
315+
}
316+
}
317+
318+
impl<T: ?Sized> Slot<Pinned, T> {
319+
/// Initialize the field.
320+
#[inline]
321+
pub fn init<E>(self, init: impl PinInit<T, E>) -> Result<DropGuard<Pinned, T>, E> {
322+
// SAFETY:
323+
// - `ptr` is valid
324+
// - when `Err` is returned, we also propagate the error without touching `ptr`;
325+
// also `self` is consumed so it cannot be touched further.
326+
// - the drop guard will not hand out `&mut` (but only `Pin<&mut T>`) it has been dropped.
327+
unsafe { init.__pinned_init(self.ptr)? };
328+
329+
// SAFETY:
330+
// - `self.ptr` is valid, properly aligned and pinned per type invariant.
331+
// - `*self.ptr` is initialized above and the ownership is transferred to the guard.
332+
Ok(unsafe { DropGuard::new(self.ptr) })
333+
}
334+
}
335+
260336
/// When a value of this type is dropped, it drops a `T`.
261337
///
262338
/// Can be forgotten to prevent the drop.

src/lib.rs

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -867,12 +867,11 @@ pub use pin_init_internal::init;
867867
#[macro_export]
868868
macro_rules! assert_pinned {
869869
($ty:ty, $field:ident, $field_ty:ty, inline) => {
870-
let _ = move |ptr: *mut $field_ty| {
871-
// SAFETY: This code is unreachable.
872-
let data = unsafe { <$ty as $crate::__internal::HasPinData>::__pin_data() };
873-
let init = $crate::__internal::AlwaysFail::<$field_ty>::new();
874-
// SAFETY: This code is unreachable.
875-
unsafe { data.$field(ptr, init) }.ok();
870+
// SAFETY: This code is unreachable.
871+
let _ = move |ptr: *mut $field_ty| unsafe {
872+
let data = <$ty as $crate::__internal::HasPinData>::__pin_data();
873+
data.$field(ptr)
874+
.init($crate::__internal::AlwaysFail::<$field_ty>::new());
876875
};
877876
};
878877

tests/ui/compile-fail/init/colon_instead_of_arrow.stderr

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,23 @@ error[E0308]: mismatched types
55
| ------------------ the found opaque type
66
...
77
21 | pin_init!(Self { bar: Bar::new() })
8-
| --- ^^^^^^^^^^ expected `Bar`, found opaque type
9-
| |
10-
| arguments to this function are incorrect
8+
| ----------------------^^^^^^^^^^---
9+
| | |
10+
| | expected `Bar`, found opaque type
11+
| arguments to this method are incorrect
1112
|
1213
= note: expected struct `Bar`
1314
found opaque type `impl pin_init::PinInit<Bar>`
14-
note: function defined here
15-
--> $RUST/core/src/ptr/mod.rs
15+
help: the return type of this call is `impl pin_init::PinInit<Bar>` due to the type of the argument passed
16+
--> tests/ui/compile-fail/init/colon_instead_of_arrow.rs:21:9
1617
|
17-
| pub const unsafe fn write<T>(dst: *mut T, src: T) {
18-
| ^^^^^
18+
21 | pin_init!(Self { bar: Bar::new() })
19+
| ^^^^^^^^^^^^^^^^^^^^^^----------^^^
20+
| |
21+
| this argument influences the return type of `write`
22+
note: method defined here
23+
--> src/__internal.rs
24+
|
25+
| pub fn write(self, value: T) -> DropGuard<P, T>
26+
| ^^^^^
27+
= note: this error originates in the macro `pin_init` (in Nightly builds, run with -Z macro-backtrace for more info)

tests/ui/compile-fail/init/field_value_wrong_type.stderr

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,21 @@ error[E0308]: mismatched types
22
--> tests/ui/compile-fail/init/field_value_wrong_type.rs:8:28
33
|
44
8 | let _ = init!(Foo { a: () });
5-
| - ^^ expected `usize`, found `()`
6-
| |
7-
| arguments to this function are incorrect
5+
| ---------------^^---
6+
| | |
7+
| | expected `usize`, found `()`
8+
| arguments to this method are incorrect
89
|
9-
note: function defined here
10-
--> $RUST/core/src/ptr/mod.rs
10+
help: the return type of this call is `()` due to the type of the argument passed
11+
--> tests/ui/compile-fail/init/field_value_wrong_type.rs:8:13
1112
|
12-
| pub const unsafe fn write<T>(dst: *mut T, src: T) {
13-
| ^^^^^
13+
8 | let _ = init!(Foo { a: () });
14+
| ^^^^^^^^^^^^^^^--^^^
15+
| |
16+
| this argument influences the return type of `write`
17+
note: method defined here
18+
--> src/__internal.rs
19+
|
20+
| pub fn write(self, value: T) -> DropGuard<P, T>
21+
| ^^^^^
22+
= note: this error originates in the macro `init` (in Nightly builds, run with -Z macro-backtrace for more info)

tests/ui/compile-fail/init/invalid_init.stderr

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
1-
error[E0277]: the trait bound `impl pin_init::PinInit<Bar>: Init<Bar>` is not satisfied
1+
error[E0277]: the trait bound `impl pin_init::PinInit<Bar>: Init<Bar, _>` is not satisfied
22
--> tests/ui/compile-fail/init/invalid_init.rs:19:16
33
|
44
18 | let _ = init!(Foo {
55
| _____________-
66
19 | | bar <- Bar::new(),
7-
| | ^^^^^^^^^^ the trait `Init<Bar>` is not implemented for `impl pin_init::PinInit<Bar>`
7+
| | ^^^^^^^^^^ the trait `Init<Bar, _>` is not implemented for `impl pin_init::PinInit<Bar>`
88
20 | | });
99
| |______- required by a bound introduced by this call
1010
|
@@ -19,3 +19,8 @@ help: the following other types implement trait `Init<T, E>`
1919
...
2020
| unsafe impl<T, E> Init<T, E> for Result<T, E> {
2121
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `Result<T, E>`
22+
note: required by a bound in `pin_init::__internal::Slot::<pin_init::__internal::Unpinned, T>::init`
23+
--> src/__internal.rs
24+
|
25+
| pub fn init<E>(self, init: impl Init<T, E>) -> Result<DropGuard<Unpinned, T>, E> {
26+
| ^^^^^^^^^^ required by this bound in `Slot::<Unpinned, T>::init`

0 commit comments

Comments
 (0)