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. 45 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. 72 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] 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. 113 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] 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] 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")] 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