|
1 |
| -use core::task::{RawWaker, RawWakerVTable, Waker}; |
2 |
| -use std::sync::Arc; |
| 1 | +//! To save on allocations, we avoid making a separate Arc Waker for every subfuture. |
| 2 | +//! Rather, we have all N Wakers share a single Arc, and use a "redirect" mechanism to allow different wakers to be distinguished. |
| 3 | +//! The mechanism works as follows. |
| 4 | +//! The Arc contains 2 things: |
| 5 | +//! - the Readiness structure ([ReadinessArray][super::array::ReadinessArray] / [ReadinessVec][super::vec::ReadinessVec]) |
| 6 | +//! - the redirect array. |
| 7 | +//! The redirect array contains N repeated copies of the pointer to the Arc itself (obtained by `Arc::into_raw`). |
| 8 | +//! The Waker for the `i`th subfuture points to the `i`th item in the redirect array. |
| 9 | +//! (i.e. the Waker pointer is `*const *const A` where `A` is the type of the item in the Arc) |
| 10 | +//! When the Waker is woken, we deref it twice (giving reference to the content of the Arc), |
| 11 | +//! and compare it to the address of the redirect slice. |
| 12 | +//! The difference tells us the index of the waker. We can then record this woken index in the Readiness. |
| 13 | +//! |
| 14 | +//! ```text |
| 15 | +//! ┌───────────────────────────┬──────────────┬──────────────┐ |
| 16 | +//! │ │ │ │ |
| 17 | +//! │ / ┌─────────────┬──────┼───────┬──────┼───────┬──────┼───────┬─────┐ \ |
| 18 | +//! ▼ / │ │ │ │ │ │ │ │ │ \ |
| 19 | +//! Arc < │ Readiness │ redirect[0] │ redirect[1] │ redirect[2] │ ... │ > |
| 20 | +//! ▲ \ │ │ │ │ │ │ / |
| 21 | +//! │ \ └─────────────┴──────▲───────┴──────▲───────┴──────▲───────┴─────┘ / |
| 22 | +//! │ │ │ │ |
| 23 | +//! └─┐ ┌───────────────┘ │ │ |
| 24 | +//! │ │ │ │ |
| 25 | +//! │ │ ┌──────────────────┘ │ |
| 26 | +//! │ │ │ │ |
| 27 | +//! │ │ │ ┌─────────────────────┘ |
| 28 | +//! │ │ │ │ |
| 29 | +//! │ │ │ │ |
| 30 | +//! ┌────┼────┬────┼──────┬────┼──────┬────┼──────┬─────┐ |
| 31 | +//! │ │ │ │ │ │ │ │ │ │ |
| 32 | +//! │ │ wakers[0] │ wakers[1] │ wakers[2] │ ... │ |
| 33 | +//! │ │ │ │ │ │ |
| 34 | +//! └─────────┴───────────┴───────────┴───────────┴─────┘ |
| 35 | +//! ``` |
3 | 36 |
|
4 |
| -// In the diagram below, `A` is the upper block. |
5 |
| -// It is a struct that implements WakeDataContainer (so either WakerVecInner or WakerArrayInner). |
6 |
| -// The lower block is either WakerVec or WakerArray. Each waker there points to a slot of wake_data in `A`. |
7 |
| -// Every one of these slots contain a pointer to the Arc wrapping `A` itself. |
8 |
| -// Wakers figure out their indices by comparing the address they are pointing to to `wake_data`'s start address. |
9 |
| -// |
10 |
| -// ┌───────────────────────────┬──────────────┬──────────────┐ |
11 |
| -// │ │ │ │ |
12 |
| -// │ / ┌─────────────┬──────┼───────┬──────┼───────┬──────┼───────┬─────┐ \ |
13 |
| -// ▼ / │ │ │ │ │ │ │ │ │ \ |
14 |
| -// Arc < │ Readiness │ wake_data[0] │ wake_data[1] │ wake_data[2] │ ... │ > |
15 |
| -// ▲ \ │ │ │ │ │ │ / |
16 |
| -// │ \ └─────────────┴──────▲───────┴──────▲───────┴──────▲───────┴─────┘ / |
17 |
| -// │ │ │ │ |
18 |
| -// └─┐ ┌───────────────┘ │ │ |
19 |
| -// │ │ │ │ |
20 |
| -// │ │ ┌──────────────────┘ │ |
21 |
| -// │ │ │ │ |
22 |
| -// │ │ │ ┌─────────────────────┘ |
23 |
| -// │ │ │ │ |
24 |
| -// │ │ │ │ |
25 |
| -// ┌────┼────┬────┼──────┬────┼──────┬────┼──────┬─────┐ |
26 |
| -// │ │ │ │ │ │ │ │ │ │ |
27 |
| -// │ Inner │ wakers[0] │ wakers[1] │ wakers[2] │ ... │ |
28 |
| -// │ │ │ │ │ │ |
29 |
| -// └─────────┴───────────┴───────────┴───────────┴─────┘ |
| 37 | +// TODO: Right now each waker gets its own redirect slot. |
| 38 | +// We can save space by making size_of::<*const _>() wakers share the same slot. |
| 39 | +// With such change, in 64-bit system, the redirect array/vec would only need ⌈N/8⌉ slots instead of N. |
30 | 40 |
|
31 |
| -// TODO: Right now each waker gets its own wake_data slot. |
32 |
| -// We can save space by making size_of::<usize>() wakers share the same slot. |
33 |
| -// With such change, in 64-bit system, the wake_data array/vec would only need ⌈N/8⌉ slots instead of N. |
| 41 | +use core::task::{RawWaker, RawWakerVTable, Waker}; |
| 42 | +use std::sync::Arc; |
34 | 43 |
|
35 |
| -pub(super) trait WakeDataContainer { |
36 |
| - /// Get the reference of the wake_data slice. This is used to compute the index. |
37 |
| - fn get_wake_data_slice(&self) -> &[*const Self]; |
| 44 | +/// A trait to be implemented on [super::WakerArray] and [super::WakerVec] for polymorphism. |
| 45 | +/// These are the type that goes in the Arc. They both contain the Readiness and the redirect array/vec. |
| 46 | +pub(super) trait SharedArcContent { |
| 47 | + /// Get the reference of the redirect slice. This is used to compute the index. |
| 48 | + fn get_redirect_slice(&self) -> &[*const Self]; |
38 | 49 | /// Called when the subfuture at the specified index should be polled.
|
| 50 | + /// Should call `Readiness::set_ready`. |
39 | 51 | fn wake_index(&self, index: usize);
|
40 | 52 | }
|
41 |
| -pub(super) unsafe fn waker_for_wake_data_slot<A: WakeDataContainer>( |
| 53 | + |
| 54 | +/// Create one waker following the mechanism described in the [module][self] doc. |
| 55 | +/// The following must be upheld for safety: |
| 56 | +/// - `pointer` must points to a slot in the redirect array. |
| 57 | +/// - that slot must contain a pointer obtained by `Arc::<A>::into_raw`. |
| 58 | +/// - the Arc must still be alive at the time this function is called. |
| 59 | +/// The following should be upheld for correct behavior: |
| 60 | +/// - calling `SharedArcContent::get_redirect_slice` on the content of the Arc should give the redirect array within which `pointer` points to. |
| 61 | +#[deny(unsafe_op_in_unsafe_fn)] |
| 62 | +pub(super) unsafe fn waker_from_redirect_position<A: SharedArcContent>( |
42 | 63 | pointer: *const *const A,
|
43 | 64 | ) -> Waker {
|
44 |
| - unsafe fn clone_waker<A: WakeDataContainer>(pointer: *const ()) -> RawWaker { |
| 65 | + /// Create a Waker from a type-erased pointer. |
| 66 | + /// The pointer must satisfy the safety constraints listed in the wrapping function's documentation. |
| 67 | + unsafe fn create_waker<A: SharedArcContent>(pointer: *const ()) -> RawWaker { |
| 68 | + // Retype the type-erased pointer. |
45 | 69 | let pointer = pointer as *const *const A;
|
46 |
| - let raw = *pointer; // This is the raw pointer of Arc<Inner>. |
47 | 70 |
|
48 | 71 | // We're creating a new Waker, so we need to increment the count.
|
49 |
| - Arc::increment_strong_count(raw); |
| 72 | + // SAFETY: The constraints listed for the wrapping function documentation means |
| 73 | + // - `*pointer` is an `*const A` obtained from `Arc::<A>::into_raw`. |
| 74 | + // - the Arc is alive. |
| 75 | + // So this operation is safe. |
| 76 | + unsafe { Arc::increment_strong_count(*pointer) }; |
50 | 77 |
|
51 | 78 | RawWaker::new(pointer as *const (), create_vtable::<A>())
|
52 | 79 | }
|
53 | 80 |
|
54 |
| - // Convert a pointer to a wake_data slot to the Arc<Inner>. |
55 |
| - unsafe fn to_arc<A: WakeDataContainer>(pointer: *const *const A) -> Arc<A> { |
56 |
| - let raw = *pointer; |
57 |
| - Arc::from_raw(raw) |
58 |
| - } |
59 |
| - unsafe fn wake<A: WakeDataContainer, const BY_REF: bool>(pointer: *const ()) { |
| 81 | + /// Invoke `SharedArcContent::wake_index` with the index in the redirect slice where this pointer points to. |
| 82 | + /// The pointer must satisfy the safety constraints listed in the wrapping function's documentation. |
| 83 | + unsafe fn wake_by_ref<A: SharedArcContent>(pointer: *const ()) { |
| 84 | + // Retype the type-erased pointer. |
60 | 85 | let pointer = pointer as *const *const A;
|
61 |
| - let arc = to_arc::<A>(pointer); |
62 |
| - // Calculate the index |
63 |
| - let index = ((pointer as usize) // This is the slot our pointer points to. |
64 |
| - - (arc.get_wake_data_slice() as *const [*const A] as *const () as usize)) // This is the starting address of wake_data. |
65 |
| - / std::mem::size_of::<*const A>(); |
66 | 86 |
|
67 |
| - arc.wake_index(index); |
| 87 | + // SAFETY: we are already requiring `pointer` to point to a slot in the redirect array. |
| 88 | + let raw: *const A = unsafe { *pointer }; |
| 89 | + // SAFETY: we are already requiring the pointer in the redirect array slot to be obtained from `Arc::into_raw`. |
| 90 | + let arc_content: &A = unsafe { &*raw }; |
68 | 91 |
|
69 |
| - // Dropping the Arc would decrement the strong count. |
70 |
| - // We only want to do that when we're not waking by ref. |
71 |
| - if BY_REF { |
72 |
| - std::mem::forget(arc); |
73 |
| - } else { |
74 |
| - std::mem::drop(arc); |
75 |
| - } |
| 92 | + // Calculate the index. |
| 93 | + // This is your familiar pointer math |
| 94 | + // `item_address = array_address + (index * item_size)` |
| 95 | + // rearranged to |
| 96 | + // `index = (item_address - array_address) / item_size`. |
| 97 | + let item_address = sptr::Strict::addr(pointer); |
| 98 | + let redirect_slice_address = sptr::Strict::addr(arc_content.get_redirect_slice().as_ptr()); |
| 99 | + let redirect_item_size = core::mem::size_of::<*const A>(); // the size of each item in the redirect slice |
| 100 | + let index = (item_address - redirect_slice_address) / redirect_item_size; |
| 101 | + |
| 102 | + arc_content.wake_index(index); |
76 | 103 | }
|
77 |
| - unsafe fn drop_waker<A: WakeDataContainer>(pointer: *const ()) { |
| 104 | + |
| 105 | + /// The pointer must satisfy the safety constraints listed in the wrapping function's documentation. |
| 106 | + unsafe fn drop_waker<A: SharedArcContent>(pointer: *const ()) { |
| 107 | + // Retype the type-erased pointer. |
78 | 108 | let pointer = pointer as *const *const A;
|
79 |
| - let arc = to_arc::<A>(pointer); |
80 |
| - // Decrement the strong count by dropping the Arc. |
81 |
| - std::mem::drop(arc); |
| 109 | + |
| 110 | + // SAFETY: we are already requiring `pointer` to point to a slot in the redirect array. |
| 111 | + let raw = unsafe { *pointer }; |
| 112 | + // SAFETY: we are already requiring the pointer in the redirect array slot to be obtained from `Arc::into_raw`. |
| 113 | + unsafe { Arc::decrement_strong_count(raw) }; |
82 | 114 | }
|
83 |
| - fn create_vtable<A: WakeDataContainer>() -> &'static RawWakerVTable { |
| 115 | + |
| 116 | + /// The pointer must satisfy the safety constraints listed in the wrapping function's documentation. |
| 117 | + unsafe fn wake<A: SharedArcContent>(pointer: *const ()) { |
| 118 | + // SAFETY: we are already requiring the constraints of `wake_by_ref` and `drop_waker`. |
| 119 | + unsafe { |
| 120 | + wake_by_ref::<A>(pointer); |
| 121 | + drop_waker::<A>(pointer); |
| 122 | + } |
| 123 | + } |
| 124 | + |
| 125 | + fn create_vtable<A: SharedArcContent>() -> &'static RawWakerVTable { |
84 | 126 | &RawWakerVTable::new(
|
85 |
| - clone_waker::<A>, |
86 |
| - wake::<A, false>, |
87 |
| - wake::<A, true>, |
| 127 | + create_waker::<A>, |
| 128 | + wake::<A>, |
| 129 | + wake_by_ref::<A>, |
88 | 130 | drop_waker::<A>,
|
89 | 131 | )
|
90 | 132 | }
|
91 |
| - Waker::from_raw(clone_waker::<A>(pointer as *const ())) |
| 133 | + // SAFETY: All our vtable functions adhere to the RawWakerVTable contract, |
| 134 | + // and we are already requiring that `pointer` is what our functions expect. |
| 135 | + unsafe { Waker::from_raw(create_waker::<A>(pointer as *const ())) } |
92 | 136 | }
|
0 commit comments