Skip to content
This repository was archived by the owner on Sep 8, 2025. It is now read-only.

Commit 4e0ed6f

Browse files
committed
eliminate ComponentInstance unsafe code in futures_and_streams.rs
This is the first step in eliminating the need for `*mut ComponentInstance` in most or all cases, instead preferring `Instance` indexes. The next step will be do remove the `StoreContextMut::with_{a,de}tached_instance[_async]` functions and update any function signatures which take both `&mut dyn VMStore` (or similar) and `&mut ComponentInstance` parameters and replace the latter with an `Instance` parameter. For the time being, such functions are _extremely_ hazardous, since it's easy to alias the `&mut ComponentInstance` by way of the store. So for now, don't do that! Signed-off-by: Joel Dice <joel.dice@fermyon.com>
1 parent 00a22a8 commit 4e0ed6f

11 files changed

Lines changed: 505 additions & 626 deletions

File tree

crates/wasmtime/src/runtime/component/concurrent.rs

Lines changed: 76 additions & 109 deletions
Original file line numberDiff line numberDiff line change
@@ -49,20 +49,20 @@
4949
5050
use {
5151
crate::{
52-
AsContext, AsContextMut, Engine, StoreContext, StoreContextMut, ValRaw,
5352
component::{
54-
HasData, HasSelf, Instance,
5553
func::{self, Func, Options},
54+
HasData, HasSelf, Instance,
5655
},
5756
store::{StoreInner, StoreOpaque, StoreToken},
5857
vm::{
59-
AsyncWasmCallState, PreviousAsyncWasmCallState, SendSyncPtr, VMFuncRef,
60-
VMMemoryDefinition, VMStore, VMStoreRawPtr,
6158
component::{CallContext, ComponentInstance, InstanceFlags, ResourceTables},
6259
mpk::{self, ProtectionMask},
60+
AsyncWasmCallState, PreviousAsyncWasmCallState, SendSyncPtr, VMFuncRef,
61+
VMMemoryDefinition, VMStore, VMStoreRawPtr,
6362
},
63+
AsContext, AsContextMut, Engine, StoreContext, StoreContextMut, ValRaw,
6464
},
65-
anyhow::{Context as _, Result, anyhow, bail},
65+
anyhow::{anyhow, bail, Context as _, Result},
6666
error_contexts::{GlobalErrorContextRefCount, LocalErrorContextRefCount},
6767
futures::{
6868
channel::oneshot,
@@ -83,36 +83,35 @@ use {
8383
marker::PhantomData,
8484
mem::{self, MaybeUninit},
8585
ops::Range,
86-
pin::{Pin, pin},
86+
pin::{pin, Pin},
8787
ptr::{self, NonNull},
8888
sync::{
89-
Arc, Mutex,
9089
atomic::{AtomicPtr, Ordering::Relaxed},
90+
Arc, Mutex,
9191
},
9292
task::{Context, Poll, Wake, Waker},
9393
vec::Vec,
9494
},
9595
table::{Table, TableDebug, TableError, TableId},
9696
wasmtime_environ::{
97-
PrimaryMap,
9897
component::{
99-
MAX_FLAT_PARAMS, MAX_FLAT_RESULTS, PREPARE_ASYNC_NO_RESULT, PREPARE_ASYNC_WITH_RESULT,
10098
RuntimeComponentInstanceIndex, StringEncoding,
10199
TypeComponentGlobalErrorContextTableIndex, TypeComponentLocalErrorContextTableIndex,
102-
TypeFutureTableIndex, TypeStreamTableIndex, TypeTupleIndex,
100+
TypeFutureTableIndex, TypeStreamTableIndex, TypeTupleIndex, MAX_FLAT_PARAMS,
101+
MAX_FLAT_RESULTS, PREPARE_ASYNC_NO_RESULT, PREPARE_ASYNC_WITH_RESULT,
103102
},
104-
fact,
103+
fact, PrimaryMap,
105104
},
106105
wasmtime_fiber::{Fiber, Suspend},
107106
};
108107

108+
pub(crate) use futures_and_streams::{
109+
lower_error_context_to_index, lower_future_to_index, lower_stream_to_index, ResourcePair,
110+
};
109111
pub use futures_and_streams::{
110112
ErrorContext, FutureReader, FutureWriter, HostFuture, HostStream, ReadBuffer, StreamReader,
111113
StreamWriter, VecBuffer, Watch, WriteBuffer,
112114
};
113-
pub(crate) use futures_and_streams::{
114-
ResourcePair, lower_error_context_to_index, lower_future_to_index, lower_stream_to_index,
115-
};
116115

117116
mod error_contexts;
118117
mod futures_and_streams;
@@ -288,7 +287,7 @@ where
288287
token: StoreToken<T>,
289288
get: fn() -> *mut dyn VMStore,
290289
get_data: fn(&mut T) -> D::Data<'_>,
291-
instance: Option<Instance>,
290+
instance: Instance,
292291
}
293292

294293
impl<T> Accessor<T> {
@@ -309,7 +308,7 @@ impl<T> Accessor<T> {
309308
/// intended scope. If it returns, the caller must be granted exclusive
310309
/// access to that store until the call to `Future::poll` for the current
311310
/// host task returns.
312-
unsafe fn new(token: StoreToken<T>, instance: Option<Instance>) -> Self {
311+
unsafe fn new(token: StoreToken<T>, instance: Instance) -> Self {
313312
Self {
314313
token,
315314
get: get_store,
@@ -377,7 +376,7 @@ where
377376
where
378377
T: 'static,
379378
{
380-
let instance = self.instance.unwrap();
379+
let instance = self.instance;
381380
let accessor = Self {
382381
token: self.token,
383382
get: self.get,
@@ -391,11 +390,6 @@ where
391390

392391
/// Retrieve the component instance of the caller.
393392
pub fn instance(&self) -> Instance {
394-
self.instance.unwrap()
395-
}
396-
397-
#[doc(hidden)]
398-
pub fn maybe_instance(&self) -> Option<Instance> {
399393
self.instance
400394
}
401395
}
@@ -515,16 +509,15 @@ enum InstanceThreadLocalState {
515509
/// they can be mutably referenced without either one aliasing the other.
516510
Detached {
517511
instance: SendSyncPtr<ComponentInstance>,
512+
handle: Instance,
518513
store: VMStoreRawPtr,
519514
},
520515
/// The specified instance's event loop is currently polling a future, and
521-
/// the instance handle is available via the `instance` field.
516+
/// the instance handle is available via the `handle` field.
522517
///
523518
/// In this case, the store and instance are in an "attached" state, meaning
524519
/// care must be taken to avoid creating mutable reference aliases.
525-
Attached {
526-
instance: SendSyncPtr<ComponentInstance>,
527-
},
520+
Attached { handle: Instance },
528521
}
529522

530523
impl fmt::Debug for InstanceThreadLocalState {
@@ -560,7 +553,10 @@ fn with_local_instance<R>(fun: impl FnOnce(&mut dyn VMStore, &mut ComponentInsta
560553
INSTANCE_STATE.with(|v| v.replace(InstanceThreadLocalState::Polling)),
561554
);
562555

563-
let InstanceThreadLocalState::Detached { instance, store } = state.0 else {
556+
let InstanceThreadLocalState::Detached {
557+
instance, store, ..
558+
} = state.0
559+
else {
564560
unreachable!("expected `Detached`; got `{:?}`", state.0)
565561
};
566562
let (store, instance) = unsafe { (&mut *store.0.as_ptr(), &mut *instance.as_ptr()) };
@@ -581,6 +577,7 @@ fn poll_with_local_instance<F: Future + Send + ?Sized>(
581577
let state = ResetInstanceThreadLocalState(INSTANCE_STATE.with(|v| {
582578
v.replace(InstanceThreadLocalState::Detached {
583579
instance: SendSyncPtr::new(instance.into()),
580+
handle: instance.instance,
584581
store: VMStoreRawPtr(store.into()),
585582
})
586583
}));
@@ -819,24 +816,7 @@ impl fmt::Debug for WorkItem {
819816
}
820817

821818
impl<T> StoreContextMut<'_, T> {
822-
/// Temporarily split the specified store and instance into a "detached"
823-
/// state (i.e. clearing the pointers they have to each other) so that they
824-
/// can both safely be exclusively referenced without accidentally aliasing
825-
/// one via the other.
826-
///
827-
/// This will take the specified instance out of the store, null out the
828-
/// pointer in the instance which points back to the store, call the
829-
/// specified function with exclusive references to both, and then reset
830-
/// everything back to the way it was before returning a result.
831-
///
832-
/// This is appropriate for use in code which needs safe, exclusive access
833-
/// to both the store and the instance simultaneously. Note that you'll
834-
/// need to temporarily re-attach the instance to the store using
835-
/// `with_attached_instance` when calling guest code or calling
836-
/// application-defined host functions, since they both expect the instance
837-
/// to be in an "attached" state.
838-
///
839-
/// See also `with_detached_instance_async` for async closures.
819+
/// Temporary code which will go away soon. Nothing to see here, folks.
840820
fn with_detached_instance<R>(
841821
&mut self,
842822
instance: &Instance,
@@ -849,18 +829,22 @@ impl<T> StoreContextMut<'_, T> {
849829
}
850830
_ => unreachable!(),
851831
}));
852-
let ptr = self.0.hide_instance(*instance);
853-
// SAFETY: We've taken the instance out of the store, so now we own
854-
// it and can take an exclusive reference to it.
855-
let reference = unsafe { &mut *ptr };
856-
reference.set_store(None);
857-
let result = fun(self.as_context_mut(), reference);
858-
reference.set_store(Some(VMStoreRawPtr(self.traitobj())));
859-
self.0.unhide_instance(*instance);
860-
result
832+
// SAFETY: This isn't safe at all, since `fun` will easily be able to
833+
// create aliases of `instance` using
834+
// e.g. `StoreOpaque::component_instance_mut`. As of this writing,
835+
// we're relying on `fun` never doing that except within a
836+
// `with_attached_instance` closure where it's sound.
837+
//
838+
// We're planning to remove `with_{a,de}tached_instance[_async]` soon
839+
// anyway and migrate the code which uses it to deal in `Instance`
840+
// parameters rather than `&mut ComponentInstance` parameters,
841+
// retrieving the latter only as necessary, with soundness enforced by
842+
// the borrow checker, at which point this will go away.
843+
let instance = unsafe { &mut *self.0[instance.0].as_mut().unwrap().instance_ptr() };
844+
fun(self.as_context_mut(), instance)
861845
}
862846

863-
/// Same as `with_detached_instance`, but for async closures.
847+
/// Temporary code which will go away soon. Nothing to see here, folks.
864848
async fn with_detached_instance_async<R>(
865849
&mut self,
866850
instance: &Instance,
@@ -873,55 +857,47 @@ impl<T> StoreContextMut<'_, T> {
873857
}
874858
_ => unreachable!(),
875859
}));
876-
let ptr = self.0.hide_instance(*instance);
877-
// SAFETY: We've taken the instance out of the store, so now we own
878-
// it and can take an exclusive reference to it.
879-
let reference = unsafe { &mut *ptr };
880-
reference.set_store(None);
881-
let result = fun(self.as_context_mut(), reference).await;
882-
reference.set_store(Some(VMStoreRawPtr(self.traitobj())));
883-
self.0.unhide_instance(*instance);
884-
result
860+
// SAFETY: See corresponding comment in `with_detached_instance`
861+
let instance = unsafe { &mut *self.0[instance.0].as_mut().unwrap().instance_ptr() };
862+
fun(self.as_context_mut(), instance).await
885863
}
886864

887-
/// Temporarily the operation performed by `with_detached_instance{_async}`.
888-
/// See the docs for those functions for details.
865+
/// Temporary code which will go away soon. Nothing to see here, folks.
889866
pub(crate) fn with_attached_instance<R>(
890867
&mut self,
891868
instance: &mut ComponentInstance,
892-
fun: impl FnOnce(StoreContextMut<'_, T>, Option<Instance>) -> R,
869+
fun: impl FnOnce(StoreContextMut<'_, T>, Instance) -> R,
893870
) -> R {
894871
let _state = ResetInstanceThreadLocalState(INSTANCE_STATE.with(|v| match v.get() {
895872
state @ InstanceThreadLocalState::None => state,
896-
state @ InstanceThreadLocalState::Polling => {
897-
if instance.instance.is_some() {
898-
v.replace(InstanceThreadLocalState::Attached {
899-
instance: SendSyncPtr::new(instance.into()),
900-
})
901-
} else {
902-
state
903-
}
904-
}
873+
InstanceThreadLocalState::Polling => v.replace(InstanceThreadLocalState::Attached {
874+
handle: instance.instance,
875+
}),
905876
_ => unreachable!(),
906877
}));
907-
if let Some(handle) = instance.instance {
908-
self.0.unhide_instance(handle);
909-
}
910-
instance.set_store(Some(VMStoreRawPtr(self.traitobj())));
911-
let result = fun(self.as_context_mut(), instance.instance);
912-
instance.set_store(None);
913-
if let Some(handle) = instance.instance {
914-
self.0.hide_instance(handle);
915-
}
916-
result
878+
fun(self.as_context_mut(), instance.instance)
917879
}
918880
}
919881

920-
impl ComponentInstance {
921-
pub(crate) fn instance(&self) -> Option<Instance> {
922-
self.instance
882+
impl StoreOpaque {
883+
/// Temporary code which will go away soon. Nothing to see here, folks.
884+
pub(crate) fn with_attached_instance<R>(
885+
&mut self,
886+
instance: &mut ComponentInstance,
887+
fun: impl FnOnce(&mut StoreOpaque, Instance) -> R,
888+
) -> R {
889+
let _state = ResetInstanceThreadLocalState(INSTANCE_STATE.with(|v| match v.get() {
890+
state @ InstanceThreadLocalState::None => state,
891+
InstanceThreadLocalState::Polling => v.replace(InstanceThreadLocalState::Attached {
892+
handle: instance.instance,
893+
}),
894+
_ => unreachable!(),
895+
}));
896+
fun(self, instance.instance)
923897
}
898+
}
924899

900+
impl ComponentInstance {
925901
fn instance_state(&mut self, instance: RuntimeComponentInstanceIndex) -> &mut InstanceState {
926902
self.concurrent_state
927903
.instance_states
@@ -1283,10 +1259,10 @@ impl ComponentInstance {
12831259
&mut dyn VMStore,
12841260
&mut ComponentInstance,
12851261
) -> Result<[MaybeUninit<ValRaw>; MAX_FLAT_PARAMS]>
1286-
+ Send
1287-
+ Sync
1288-
+ 'static
1289-
+ use<T> {
1262+
+ Send
1263+
+ Sync
1264+
+ 'static
1265+
+ use<T> {
12901266
let token = StoreToken::new(store);
12911267
move |store: &mut dyn VMStore, instance: &mut ComponentInstance| {
12921268
let mut storage = [MaybeUninit::uninit(); MAX_FLAT_PARAMS];
@@ -1887,7 +1863,7 @@ impl ComponentInstance {
18871863
// thread-local variable which `poll_with_state` will populate and reset
18881864
// with valid pointers to the store data and the store itself each time
18891865
// the returned future is polled, respectively.
1890-
let mut accessor = unsafe { Accessor::new(token, self.instance()) };
1866+
let mut accessor = unsafe { Accessor::new(token, self.instance) };
18911867
let mut future = Box::pin(async move { closure(&mut accessor, params).await });
18921868
Box::pin(future::poll_fn(move |cx| {
18931869
poll_with_state(token, cx, future.as_mut())
@@ -3358,7 +3334,7 @@ impl Instance {
33583334
.with_detached_instance(self, |store, instance| {
33593335
let token = StoreToken::new(store);
33603336
// SAFETY: See corresponding comment in `ComponentInstance::wrap_call`.
3361-
let mut accessor = unsafe { Accessor::new(token, instance.instance()) };
3337+
let mut accessor = unsafe { Accessor::new(token, instance.instance) };
33623338
let mut future = Box::pin(async move { fun(&mut accessor).await });
33633339
Box::pin(future::poll_fn(move |cx| {
33643340
poll_with_state(token, cx, future.as_mut())
@@ -3382,8 +3358,7 @@ impl Instance {
33823358
) -> AbortHandle {
33833359
let mut store = store.as_context_mut();
33843360
// SAFETY: TODO
3385-
let accessor =
3386-
unsafe { Accessor::new(StoreToken::new(store.as_context_mut()), Some(*self)) };
3361+
let accessor = unsafe { Accessor::new(StoreToken::new(store.as_context_mut()), *self) };
33873362
self.spawn_with_accessor(store, accessor, task)
33883363
}
33893364

@@ -4751,7 +4726,7 @@ fn for_any_lift<
47514726
///
47524727
/// See `Instance::run` for details.
47534728
fn checked<F: Future + Send + 'static>(
4754-
instance: SendSyncPtr<ComponentInstance>,
4729+
instance: Instance,
47554730
fut: F,
47564731
) -> impl Future<Output = F::Output> + Send + 'static {
47574732
let mut fut = Box::pin(fut);
@@ -4764,12 +4739,8 @@ fn checked<F: Future + Send + 'static>(
47644739
";
47654740
INSTANCE_STATE.with(|v| {
47664741
let matched = match v.get() {
4767-
InstanceThreadLocalState::Detached {
4768-
instance: local, ..
4769-
}
4770-
| InstanceThreadLocalState::Attached { instance: local } => {
4771-
local.as_ptr() == instance.as_ptr()
4772-
}
4742+
InstanceThreadLocalState::Detached { handle, .. }
4743+
| InstanceThreadLocalState::Attached { handle } => handle.0 == instance.0,
47734744
InstanceThreadLocalState::None => false,
47744745
InstanceThreadLocalState::Polling => unreachable!(),
47754746
};
@@ -5299,12 +5270,8 @@ pub(crate) fn queue_call<T: 'static, R: Send + 'static>(
52995270

53005271
queue_call0(store.as_context_mut(), handle, task, param_count)?;
53015272

5302-
let func_data = &store[handle.0];
5303-
let instance = func_data.instance;
5304-
let instance_ptr = store.0[instance.0].as_ref().unwrap().instance_ptr();
5305-
53065273
Ok(checked(
5307-
SendSyncPtr::new(NonNull::new(instance_ptr).unwrap()),
5274+
store[handle.0].instance,
53085275
rx.map(|result| {
53095276
result
53105277
.map(|v| *v.downcast().unwrap())

0 commit comments

Comments
 (0)