From be1d9668aa25ced730e3025ee94f9c1a45104128 Mon Sep 17 00:00:00 2001 From: Matheus Consoli Date: Mon, 14 Nov 2022 16:16:10 -0300 Subject: [PATCH 1/2] Remove `MaybeDone` from `tuple::join` --- Cargo.toml | 1 + src/future/join/tuple.rs | 123 +++++++++++++++++++++++++++------------ 2 files changed, 87 insertions(+), 37 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 9ada005..71fcac7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,6 +23,7 @@ harness = false [dependencies] bitvec = { version = "1.0.1", default-features = false, features = ["alloc"] } futures-core = "0.3" +paste = "1.0.9" pin-project = "1.0.8" [dev-dependencies] diff --git a/src/future/join/tuple.rs b/src/future/join/tuple.rs index 9c91660..f18a2e3 100644 --- a/src/future/join/tuple.rs +++ b/src/future/join/tuple.rs @@ -1,28 +1,37 @@ use super::Join as JoinTrait; -use crate::utils::MaybeDone; +use crate::utils::PollState; use core::fmt::{self, Debug}; use core::future::{Future, IntoFuture}; +use core::mem::MaybeUninit; use core::pin::Pin; use core::task::{Context, Poll}; -use pin_project::pin_project; +use pin_project::{pin_project, pinned_drop}; -macro_rules! impl_merge_tuple { +use paste::paste; + +macro_rules! impl_join_tuple { ($StructName:ident $($F:ident)*) => { - /// Waits for two similarly-typed futures to complete. - /// - /// This `struct` is created by the [`join`] method on the [`Join`] trait. See - /// its documentation for more. - /// - /// [`join`]: crate::future::Join::join - /// [`Join`]: crate::future::Join - #[pin_project] - #[must_use = "futures do nothing unless you `.await` or poll them"] - #[allow(non_snake_case)] - pub struct $StructName<$($F: Future),*> { - done: bool, - $(#[pin] $F: MaybeDone<$F>,)* + paste!{ + /// Waits for two similarly-typed futures to complete. + /// + /// This `struct` is created by the [`join`] method on the [`Join`] trait. See + /// its documentation for more. + /// + /// [`join`]: crate::future::Join::join + /// [`Join`]: crate::future::Join + #[pin_project(PinnedDrop)] + #[must_use = "futures do nothing unless you `.await` or poll them"] + #[allow(non_snake_case)] + pub struct $StructName<$($F: Future),*> { + done: bool, + $( + #[pin] $F: $F, + [<$F _out>]: MaybeUninit<$F::Output>, + [<$F _state>]: PollState, + )* + } } impl<$($F),*> Debug for $StructName<$($F),*> @@ -31,9 +40,11 @@ macro_rules! impl_merge_tuple { $F::Output: Debug, )* { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_tuple("Join") - $(.field(&self.$F))* - .finish() + paste!{ + f.debug_tuple("Join") + $(.field(&self.$F).field(&self.[<$F _state >]))* + .finish() + } } } @@ -50,11 +61,24 @@ macro_rules! impl_merge_tuple { let mut this = self.project(); assert!(!*this.done, "Futures must not be polled after completing"); - $(all_done &= this.$F.as_mut().poll(cx).is_ready();)* + // Poll futures + paste! { + $( + if this.[<$F _state>].is_pending() { + if let Poll::Ready(out) = this.$F.poll(cx) { + *this.[<$F _out>] = MaybeUninit::new(out); + *this.[<$F _state>] = PollState::Done; + } + } + all_done &= this.[<$F _state>].is_done(); + )* + } if all_done { *this.done = true; - Poll::Ready(($(this.$F.take().unwrap()),*)) + paste! { + Poll::Ready(($( unsafe { this.[<$F _out>].assume_init_read() }),*)) + } } else { Poll::Pending } @@ -71,28 +95,53 @@ macro_rules! impl_merge_tuple { fn join(self) -> Self::Future { let ($($F,)*): ($($F,)*) = self; - $StructName { - done: false, - $($F: MaybeDone::new($F.into_future())),* + paste! { + $StructName { + done: false, + $( + $F: $F.into_future(), + [<$F _out>]: MaybeUninit::uninit(), + [<$F _state>]: PollState::default(), + )* + } + } + } + } + + #[pinned_drop] + impl<$($F,)*> PinnedDrop for $StructName<$($F,)*> + where $( + $F: Future, + )* { + fn drop(self: Pin<&mut Self>) { + let _this = self.project(); + + paste! { + $( + if _this.[<$F _state>].is_done() { + // SAFETY: if the future is marked as done, we can safelly drop its out + unsafe { _this.[<$F _out>].assume_init_drop() }; + } + )* } } } }; } -impl_merge_tuple! { Join0 } -impl_merge_tuple! { Join1 A } -impl_merge_tuple! { Join2 A B } -impl_merge_tuple! { Join3 A B C } -impl_merge_tuple! { Join4 A B C D } -impl_merge_tuple! { Join5 A B C D E } -impl_merge_tuple! { Join6 A B C D E F } -impl_merge_tuple! { Join7 A B C D E F G } -impl_merge_tuple! { Join8 A B C D E F G H } -impl_merge_tuple! { Join9 A B C D E F G H I } -impl_merge_tuple! { Join10 A B C D E F G H I J } -impl_merge_tuple! { Join11 A B C D E F G H I J K } -impl_merge_tuple! { Join12 A B C D E F G H I J K L } +impl_join_tuple! { Join0 } +impl_join_tuple! { Join1 A } +impl_join_tuple! { Join2 A B } +impl_join_tuple! { Join3 A B C } +impl_join_tuple! { Join4 A B C D } +impl_join_tuple! { Join5 A B C D E } +impl_join_tuple! { Join6 A B C D E F } +impl_join_tuple! { Join7 A B C D E F G } +impl_join_tuple! { Join8 A B C D E F G H } +impl_join_tuple! { Join9 A B C D E F G H I } +impl_join_tuple! { Join10 A B C D E F G H I J } +impl_join_tuple! { Join11 A B C D E F G H I J K } +impl_join_tuple! { Join12 A B C D E F G H I J K L } #[cfg(test)] mod test { From 32a52330260bc8febcf16f872e439fcb699e0a44 Mon Sep 17 00:00:00 2001 From: Matheus Consoli Date: Tue, 15 Nov 2022 20:57:28 -0300 Subject: [PATCH 2/2] Rework tuple::join impl --- Cargo.toml | 1 - src/future/join/tuple.rs | 182 ++++++++++++++++++++++----------------- src/utils/poll_state.rs | 13 +++ src/utils/tuple.rs | 4 + 4 files changed, 121 insertions(+), 79 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 71fcac7..9ada005 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,7 +23,6 @@ harness = false [dependencies] bitvec = { version = "1.0.1", default-features = false, features = ["alloc"] } futures-core = "0.3" -paste = "1.0.9" pin-project = "1.0.8" [dev-dependencies] diff --git a/src/future/join/tuple.rs b/src/future/join/tuple.rs index f18a2e3..1b1fee6 100644 --- a/src/future/join/tuple.rs +++ b/src/future/join/tuple.rs @@ -1,5 +1,5 @@ use super::Join as JoinTrait; -use crate::utils::PollState; +use crate::utils::{self, PollState, PollStates}; use core::fmt::{self, Debug}; use core::future::{Future, IntoFuture}; @@ -7,31 +7,95 @@ use core::mem::MaybeUninit; use core::pin::Pin; use core::task::{Context, Poll}; -use pin_project::{pin_project, pinned_drop}; +use pin_project::pin_project; -use paste::paste; +macro_rules! maybe_poll { + ($idx:tt, $len:ident, $this:ident, $fut:ident, $cx:ident) => { + if $this.states[$idx].is_pending() { + if let Poll::Ready(out) = $this.$fut.poll($cx) { + $this.outputs.$idx = MaybeUninit::new(out); + $this.states[$idx] = PollState::Done; + *$this.len -= 1; + } + } + }; +} + +macro_rules! poll_all_pending { + (@inner 0, $len:ident, $this:ident, $cx:ident, ($fut:ident, $($rest:ident,)*)) => { + maybe_poll!(0, $len, $this, $fut, $cx); + poll_all_pending!(@inner 1, $len, $this, $cx, ($($rest,)*)); + }; + (@inner 1, $len:ident, $this:ident, $cx:ident, ($fut:ident, $($rest:ident,)*)) => { + maybe_poll!(1, $len, $this, $fut, $cx); + poll_all_pending!(@inner 2, $len, $this, $cx, ($($rest,)*)); + }; + (@inner 2, $len:ident, $this:ident, $cx:ident, ($fut:ident, $($rest:ident,)*)) => { + maybe_poll!(2, $len, $this, $fut, $cx); + poll_all_pending!(@inner 3, $len, $this, $cx, ($($rest,)*)); + }; + (@inner 3, $len:ident, $this:ident, $cx:ident, ($fut:ident, $($rest:ident,)*)) => { + maybe_poll!(3, $len, $this, $fut, $cx); + poll_all_pending!(@inner 4, $len, $this, $cx, ($($rest,)*)); + }; + (@inner 4, $len:ident, $this:ident, $cx:ident, ($fut:ident, $($rest:ident,)*)) => { + maybe_poll!(4, $len, $this, $fut, $cx); + poll_all_pending!(@inner 5, $len, $this, $cx, ($($rest,)*)); + }; + (@inner 5, $len:ident, $this:ident, $cx:ident, ($fut:ident, $($rest:ident,)*)) => { + maybe_poll!(5, $len, $this, $fut, $cx); + poll_all_pending!(@inner 6, $len, $this, $cx, ($($rest,)*)); + }; + (@inner 6, $len:ident, $this:ident, $cx:ident, ($fut:ident, $($rest:ident,)*)) => { + maybe_poll!(6, $len, $this, $fut, $cx); + poll_all_pending!(@inner 7, $len, $this, $cx, ($($rest,)*)); + }; + (@inner 7, $len:ident, $this:ident, $cx:ident, ($fut:ident, $($rest:ident,)*)) => { + maybe_poll!(7, $len, $this, $fut, $cx); + poll_all_pending!(@inner 8, $len, $this, $cx, ($($rest,)*)); + }; + (@inner 8, $len:ident, $this:ident, $cx:ident, ($fut:ident, $($rest:ident,)*)) => { + maybe_poll!(8, $len, $this, $fut, $cx); + poll_all_pending!(@inner 9, $len, $this, $cx, ($($rest,)*)); + }; + (@inner 9, $len:ident, $this:ident, $cx:ident, ($fut:ident, $($rest:ident,)*)) => { + maybe_poll!(9, $len, $this, $fut, $cx); + poll_all_pending!(@inner 10, $len, $this, $cx, ($($rest,)*)); + }; + (@inner 10, $len:ident, $this:ident, $cx:ident, ($fut:ident, $($rest:ident,)*)) => { + maybe_poll!(10, $len, $this, $fut, $cx); + poll_all_pending!(@inner 11, $len, $this, $cx, ($($rest,)*)); + }; + (@inner 11, $len:ident, $this:ident, $cx:ident, ($fut:ident, $($rest:ident,)*)) => { + maybe_poll!(11, $len, $this, $fut, $cx); + poll_all_pending!(@inner 12, $len, $this, $cx, ($($rest,)*)); + }; + (@inner 12, $len:ident, $this:ident, $cx:ident, ($fut:ident, $($rest:ident,)*)) => { + maybe_poll!(12, $len, $this, $fut, $cx); + }; + (@inner $ignore:literal, $len:ident, $this:ident, $cx:ident, ()) => { }; + ($len:ident, $this:ident, $cx:ident, $($F:ident,)*) => { + poll_all_pending!(@inner 0, $len, $this, $cx, ($($F,)*)); + }; +} macro_rules! impl_join_tuple { ($StructName:ident $($F:ident)*) => { - paste!{ - /// Waits for two similarly-typed futures to complete. - /// - /// This `struct` is created by the [`join`] method on the [`Join`] trait. See - /// its documentation for more. - /// - /// [`join`]: crate::future::Join::join - /// [`Join`]: crate::future::Join - #[pin_project(PinnedDrop)] - #[must_use = "futures do nothing unless you `.await` or poll them"] - #[allow(non_snake_case)] - pub struct $StructName<$($F: Future),*> { - done: bool, - $( - #[pin] $F: $F, - [<$F _out>]: MaybeUninit<$F::Output>, - [<$F _state>]: PollState, - )* - } + /// Waits for two similarly-typed futures to complete. + /// + /// This `struct` is created by the [`join`] method on the [`Join`] trait. See + /// its documentation for more. + /// + /// [`join`]: crate::future::Join::join + /// [`Join`]: crate::future::Join + #[pin_project] + #[must_use = "futures do nothing unless you `.await` or poll them"] + #[allow(non_snake_case)] + pub struct $StructName<$($F: Future),*> { + len: u32, + $(#[pin] $F: $F,)* + outputs: ($(MaybeUninit<$F::Output>,)*), + states: PollStates, } impl<$($F),*> Debug for $StructName<$($F),*> @@ -40,11 +104,10 @@ macro_rules! impl_join_tuple { $F::Output: Debug, )* { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - paste!{ - f.debug_tuple("Join") - $(.field(&self.$F).field(&self.[<$F _state >]))* - .finish() - } + f.debug_tuple("Join") + .field(&($(&self.$F,)*)) + .field(&self.states) + .finish() } } @@ -52,33 +115,18 @@ macro_rules! impl_join_tuple { #[allow(unused_parens)] #[allow(unused_variables)] impl<$($F: Future),*> Future for $StructName<$($F),*> { - type Output = ($($F::Output),*); + type Output = ($($F::Output,)*); fn poll( self: Pin<&mut Self>, cx: &mut Context<'_> ) -> Poll { - let mut all_done = true; let mut this = self.project(); - assert!(!*this.done, "Futures must not be polled after completing"); - - // Poll futures - paste! { - $( - if this.[<$F _state>].is_pending() { - if let Poll::Ready(out) = this.$F.poll(cx) { - *this.[<$F _out>] = MaybeUninit::new(out); - *this.[<$F _state>] = PollState::Done; - } - } - all_done &= this.[<$F _state>].is_done(); - )* - } - if all_done { - *this.done = true; - paste! { - Poll::Ready(($( unsafe { this.[<$F _out>].assume_init_read() }),*)) - } + poll_all_pending!(LEN, this, cx, $($F,)*); + + if *this.len <= 0 { + let out = unsafe {(this.outputs as *const _ as *const ($($F::Output,)*)).read()}; + Poll::Ready(out) } else { Poll::Pending } @@ -90,39 +138,17 @@ macro_rules! impl_join_tuple { where $( $F: IntoFuture, )* { - type Output = ($($F::Output),*); + type Output = ($($F::Output,)*); type Future = $StructName<$($F::IntoFuture),*>; fn join(self) -> Self::Future { let ($($F,)*): ($($F,)*) = self; - paste! { - $StructName { - done: false, - $( - $F: $F.into_future(), - [<$F _out>]: MaybeUninit::uninit(), - [<$F _state>]: PollState::default(), - )* - } - } - } - } - - #[pinned_drop] - impl<$($F,)*> PinnedDrop for $StructName<$($F,)*> - where $( - $F: Future, - )* { - fn drop(self: Pin<&mut Self>) { - let _this = self.project(); - - paste! { - $( - if _this.[<$F _state>].is_done() { - // SAFETY: if the future is marked as done, we can safelly drop its out - unsafe { _this.[<$F _out>].assume_init_drop() }; - } - )* + const LEN: u32 = utils::tuple_len!($($F,)*); + $StructName { + len: LEN, + $($F: $F.into_future(),)* + outputs: ($(MaybeUninit::<$F::Output>::uninit(),)*), + states: PollStates::new(LEN as usize), } } } @@ -159,7 +185,7 @@ mod test { fn join_1() { futures_lite::future::block_on(async { let a = future::ready("hello"); - assert_eq!((a,).join().await, ("hello")); + assert_eq!((a,).join().await, ("hello",)); }); } diff --git a/src/utils/poll_state.rs b/src/utils/poll_state.rs index e9497f4..0aeadab 100644 --- a/src/utils/poll_state.rs +++ b/src/utils/poll_state.rs @@ -69,6 +69,19 @@ pub(crate) enum PollStates { Boxed(Box<[PollState]>), } +impl core::fmt::Debug for PollStates { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Inline(len, states) => f + // .debug_tuple("Inline") + .debug_list() + .entries(&states[..(*len as usize)]) + .finish(), + Self::Boxed(states) => f.debug_list().entries(&**states).finish(), + } + } +} + impl PollStates { pub(crate) fn new(len: usize) -> Self { assert!(MAX_INLINE_ENTRIES <= u8::MAX as usize); diff --git a/src/utils/tuple.rs b/src/utils/tuple.rs index 5e8dc4e..d50b1d5 100644 --- a/src/utils/tuple.rs +++ b/src/utils/tuple.rs @@ -1,6 +1,10 @@ /// Compute the number of permutations for a number /// during compilation. pub(crate) const fn permutations(mut num: u32) -> u32 { + if num == 0 { + return 0; + } + let mut total = 1; loop { total *= num;