1 //! A small self-contained module to manage passing a `&mut dyn VMStore` across
2 //! function boundaries without it actually being a function parameter.
3 //!
4 //! Much of concurrent.rs and futures_and_streams.rs work with `Future` which
5 //! does not allow customizing state being passed to each poll of a future. In
6 //! Wasmtime, however, the mutable store is available during a calls to
7 //! `Future::poll`, but not across calls of `Future::poll`. That means that
8 //! effectively what we would ideally want is to thread `&mut dyn VMStore` as a
9 //! parameter to futures, but that's not possible with Rust's future trait.
10 //!
11 //! This module is the workaround to otherwise enable this which is to use
12 //! thread-local-storage instead to pass around this pointer. The goal of this
13 //! module is to enable the `set` API to pretend like it's passing a pointer as
14 //! a parameter to a closure and then `get` can be called to acquire this
15 //! parameter. This module is intentionally small and isolated to keep the
16 //! internal implementation details private and reduce the surface area that
17 //! must be audited for the `unsafe` blocks contained within.
18 
19 use crate::runtime::vm::VMStore;
20 use core::cell::Cell;
21 use core::mem;
22 use core::ptr::NonNull;
23 
24 std::thread_local! {
25     // Note that care is currently taken to minimize the size of this TLS
26     // variable as it's expected we'll refactor this in the future and have to
27     // plumb it to the platform abstraction layer of Wasmtime eventually where
28     // we want as minimal an impact as possible. Thus this TLS variable is
29     // a single pointer.
30     static STORAGE: Cell<Option<NonNull<SetStorage>>> = const { Cell::new(None) };
31 }
32 
33 enum SetStorage {
34     Present(NonNull<dyn VMStore>),
35     Taken,
36 }
37 
38 /// Configures `store` to be available for the duration of `f` through calls to
39 /// the [`get`] function below.
40 ///
41 /// This function will replace any prior state that was configured and overwrite
42 /// it. Upon `f` returning the previous state will be restored. This function
43 /// intentionally borrows `store` for the entire duration of `f` meaning that
44 /// `f` is not allowed to access `store` via Rust's borrow checker.
set<R>(store: &mut dyn VMStore, f: impl FnOnce() -> R) -> R45 pub fn set<R>(store: &mut dyn VMStore, f: impl FnOnce() -> R) -> R {
46     let mut storage = SetStorage::Present(NonNull::from(store));
47     let _reset = ResetTls(STORAGE.with(|s| s.replace(Some(NonNull::from(&mut storage)))));
48     return f();
49 
50     struct ResetTls(Option<NonNull<SetStorage>>);
51 
52     impl Drop for ResetTls {
53         fn drop(&mut self) {
54             STORAGE.with(|s| s.set(self.0));
55         }
56     }
57 }
58 
59 /// Acquires a reference to the previous store configured via [`set`] above,
60 /// yielding this reference to the closure `f provided here.
61 ///
62 /// This function will "take" the store from thread-local-storage for the
63 /// duration of the `get` function here. This "take" operation means that
64 /// recursive calls to `get` here will fail as the second one won't be able to
65 /// re-acquire the same pointer the first one has (due to it having `&mut`
66 /// exclusive access.
67 ///
68 /// # Panics
69 ///
70 /// This function will panic if [`set`] has not been previously called or if the
71 /// current pointer is taken by a previous call to [`get`] on the stack.
get<R>(f: impl FnOnce(&mut dyn VMStore) -> R) -> R72 pub fn get<R>(f: impl FnOnce(&mut dyn VMStore) -> R) -> R {
73     try_get(|val| match val {
74         TryGet::Some(store) => f(store),
75         TryGet::None => get_failed(false),
76         TryGet::Taken => get_failed(true),
77     })
78 }
79 
80 #[cold]
get_failed(taken: bool) -> !81 fn get_failed(taken: bool) -> ! {
82     if taken {
83         panic!(
84             "attempted to recursively call `Accessor::with` when the pointer \
85             was already taken by a previous call to `Accessor::with`; try \
86             using `RUST_BACKTRACE=1` to find two stack frames to \
87             `Accessor::with` on the stack"
88         );
89     } else {
90         panic!(
91             "`Accessor::with` was called when the TLS pointer was not \
92              previously set; this is likely a bug in Wasmtime and we would \
93              appreciate an issue being filed to help fix this."
94         );
95     }
96 }
97 
98 /// Values yielded to the [`try_get`] closure as an argument.
99 pub enum TryGet<'a> {
100     /// The [`set`] API was not previously called, so there is no store
101     /// available at all.
102     None,
103     /// The [`set`] API was previously called but it was then subsequently taken
104     /// via a call to [`get`] meaning it's not available.
105     Taken,
106     /// The [`set`] API was previously called and this is the store that it was
107     /// called with.
108     Some(&'a mut dyn VMStore),
109 }
110 
111 /// Same as [`get`] except that this does not panic if `set` has not been
112 /// called.
try_get<R>(f: impl FnOnce(TryGet<'_>) -> R) -> R113 pub fn try_get<R>(f: impl FnOnce(TryGet<'_>) -> R) -> R {
114     // SAFETY: This is The Unsafe Block of this module on which everything
115     // hinges. The overall idea is that the pointer previously provided to
116     // `set` is passed to the closure here but only at most once because it's
117     // passed mutably. Thus there's a number of things that this takes care of:
118     //
119     // * The lifetime in `TryGet` that's handed out is anonymous via the
120     //   type signature of `f`, meaning that it cannot be safely persisted
121     //   outside that closure. That means that once `f` is returned this
122     //   function has exclusive access to the store again.
123     //
124     // * If `STORAGE` is not set then that means `set` has not been configured,
125     //   thus `TryGet::None` is yielded.
126     //
127     // * If `STORAGE` is set then we're guaranteed it's set for the entire
128     //   lifetime of this function call, and we're also guaranteed that the
129     //   pointer stored in there is the same pointer we'll be modifying for
130     //   this whole function call.
131     //
132     // * The `STORAGE` pointer is read/written only in a scoped manner here and
133     //   borrows of this value are not persisted for very long.
134     //
135     // With all of that put together it should make it such that this is a safe
136     // reborrow of the store provided to `set` to pass to the closure `f` here.
137     unsafe {
138         let storage = STORAGE.with(|s| s.get());
139         let _reset;
140         let val = match storage {
141             Some(mut storage) => match mem::replace(storage.as_mut(), SetStorage::Taken) {
142                 SetStorage::Taken => TryGet::Taken,
143                 SetStorage::Present(mut ptr) => {
144                     _reset = ResetStorage(storage, ptr);
145                     TryGet::Some(ptr.as_mut())
146                 }
147             },
148             None => TryGet::None,
149         };
150         return f(val);
151     }
152 
153     struct ResetStorage(NonNull<SetStorage>, NonNull<dyn VMStore>);
154 
155     impl Drop for ResetStorage {
156         fn drop(&mut self) {
157             unsafe {
158                 *self.0.as_mut() = SetStorage::Present(self.1);
159             }
160         }
161     }
162 }
163 
164 #[cfg(test)]
165 mod tests {
166     use super::{TryGet, get, set, try_get};
167     use crate::{AsContextMut, Engine, Store};
168 
169     #[test]
test_simple()170     fn test_simple() {
171         let engine = Engine::default();
172         let mut store = Store::new(&engine, ());
173 
174         set(store.as_context_mut().0, || {
175             get(|_| {});
176             try_get(|t| {
177                 assert!(matches!(t, TryGet::Some(_)));
178             });
179         });
180     }
181 
182     #[test]
test_try_get()183     fn test_try_get() {
184         let engine = Engine::default();
185         let mut store = Store::new(&engine, ());
186 
187         try_get(|t| {
188             assert!(matches!(t, TryGet::None));
189             try_get(|t| {
190                 assert!(matches!(t, TryGet::None));
191             });
192         });
193         set(store.as_context_mut().0, || {
194             get(|_| {
195                 try_get(|t| {
196                     assert!(matches!(t, TryGet::Taken));
197                     try_get(|t| {
198                         assert!(matches!(t, TryGet::Taken));
199                     });
200                 });
201             });
202             try_get(|t| {
203                 assert!(matches!(t, TryGet::Some(_)));
204                 try_get(|t| {
205                     assert!(matches!(t, TryGet::Taken));
206                     try_get(|t| {
207                         assert!(matches!(t, TryGet::Taken));
208                     });
209                 });
210             });
211             try_get(|t| {
212                 assert!(matches!(t, TryGet::Some(_)));
213                 try_get(|t| {
214                     assert!(matches!(t, TryGet::Taken));
215                 });
216             });
217         });
218         try_get(|t| {
219             assert!(matches!(t, TryGet::None));
220         });
221     }
222 
223     #[test]
224     #[should_panic(expected = "attempted to recursively call")]
test_get_panic()225     fn test_get_panic() {
226         let engine = Engine::default();
227         let mut store = Store::new(&engine, ());
228 
229         set(store.as_context_mut().0, || {
230             get(|_| {
231                 get(|_| {
232                     panic!("should not get here");
233                 });
234             });
235         });
236     }
237 }
238