wasmtime/runtime/component/concurrent/tls.rs
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
19use crate::runtime::vm::VMStore;
20use core::cell::Cell;
21use core::mem;
22use core::ptr::NonNull;
23
24std::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
33enum 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.
45pub 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.
72pub 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]
81fn 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.
99pub 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.
113pub 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)]
165mod 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}