Skip to content

Commit 4233014

Browse files
authored
Refactor component host/libcalls (#10959)
In the spirit of making Wasmtime's internals safer this is a step forward for components to a new paradigm for how libcalls/host functions are implemented. Previously `*mut ComponentInstance` was liberally used but this meant that situations would often simultaneously have `&mut ComponentInstance` and `&mut StoreOpaque` accessible in the same function and there was no prevention of going from the store to the component instance, acquiring two aliasing mutable references (which would be unsound). The refactoring applied here is to redefine the entrypoints from the guest back into the host to operate on `&mut dyn VMStore` (or `StoreContextMut<'_, T>`) plus `wasmtime::component::Instance`. This index-based approach means that there's no aliasing of component instances and stores and the `Instance` type can be used to look up anything within the store that's necessary. This refactoring originated in the wasip3-prototyping repository and has been used to remove a good deal of `unsafe` code now that `Instance` is effectively safe to pass around and the store was already passed around anyway everywhere. In the future I plan to apply a similar paradigm shift for core instances as well, but that'll require some more finesse for all the bits and bobs that core wasm does.
1 parent 75c8682 commit 4233014

8 files changed

Lines changed: 310 additions & 210 deletions

File tree

crates/wasmtime/src/runtime/component/func/host.rs

Lines changed: 21 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ use crate::component::storage::slice_to_storage_mut;
44
use crate::component::{ComponentNamedList, ComponentType, Instance, Lift, Lower, Val};
55
use crate::prelude::*;
66
use crate::runtime::vm::component::{
7-
InstanceFlags, VMComponentContext, VMLowering, VMLoweringCallee,
7+
ComponentInstance, InstanceFlags, VMComponentContext, VMLowering, VMLoweringCallee,
88
};
99
use crate::runtime::vm::{VMFuncRef, VMGlobalDefinition, VMMemoryDefinition, VMOpaqueContext};
1010
use crate::{AsContextMut, CallHook, StoreContextMut, ValRaw};
@@ -13,8 +13,8 @@ use core::any::Any;
1313
use core::mem::{self, MaybeUninit};
1414
use core::ptr::NonNull;
1515
use wasmtime_environ::component::{
16-
CanonicalAbiInfo, ComponentTypes, InterfaceType, MAX_FLAT_PARAMS, MAX_FLAT_RESULTS,
17-
StringEncoding, TypeFuncIndex,
16+
CanonicalAbiInfo, InterfaceType, MAX_FLAT_PARAMS, MAX_FLAT_RESULTS, StringEncoding,
17+
TypeFuncIndex,
1818
};
1919

2020
pub struct HostFunc {
@@ -66,11 +66,10 @@ impl HostFunc {
6666
{
6767
let data = data.as_ptr() as *const F;
6868
unsafe {
69-
call_host_and_handle_result::<T>(cx, |store, instance, types| {
69+
call_host_and_handle_result::<T>(cx, |store, instance| {
7070
call_host(
7171
store,
7272
instance,
73-
types,
7473
TypeFuncIndex::from_u32(ty),
7574
InstanceFlags::from_raw(flags),
7675
memory,
@@ -148,7 +147,6 @@ where
148147
unsafe fn call_host<T, Params, Return, F>(
149148
mut cx: StoreContextMut<'_, T>,
150149
instance: Instance,
151-
types: &Arc<ComponentTypes>,
152150
ty: TypeFuncIndex,
153151
mut flags: InstanceFlags,
154152
memory: *mut VMMemoryDefinition,
@@ -199,6 +197,7 @@ where
199197
bail!("cannot leave component instance");
200198
}
201199

200+
let types = cx[instance.id()].component().types().clone();
202201
let ty = &types[ty];
203202
let param_tys = InterfaceType::Tuple(ty.params);
204203
let result_tys = InterfaceType::Tuple(ty.results);
@@ -225,13 +224,13 @@ where
225224
Storage::Indirect(slice_to_storage_mut(storage).assume_init_ref())
226225
}
227226
};
228-
let mut lift = LiftContext::new(cx.0, &options, types, instance);
227+
let mut lift = LiftContext::new(cx.0, &options, &types, instance);
229228
lift.enter_call();
230229
let params = storage.lift_params(&mut lift, param_tys)?;
231230

232231
let ret = closure(cx.as_context_mut(), params)?;
233232
flags.set_may_leave(false);
234-
let mut lower = LowerContext::new(cx, &options, types, instance);
233+
let mut lower = LowerContext::new(cx, &options, &types, instance);
235234
storage.lower_results(&mut lower, result_tys, ret)?;
236235
flags.set_may_leave(true);
237236

@@ -308,30 +307,27 @@ fn validate_inbounds<T: ComponentType>(memory: &[u8], ptr: &ValRaw) -> Result<us
308307

309308
unsafe fn call_host_and_handle_result<T>(
310309
cx: NonNull<VMOpaqueContext>,
311-
func: impl FnOnce(StoreContextMut<'_, T>, Instance, &Arc<ComponentTypes>) -> Result<()>,
310+
func: impl FnOnce(StoreContextMut<'_, T>, Instance) -> Result<()>,
312311
) -> bool
313312
where
314313
T: 'static,
315314
{
316315
let cx = VMComponentContext::from_opaque(cx);
317-
let instance = cx.as_ref().instance();
318-
let types = (*instance).component().types();
319-
let raw_store = (*instance).store();
320-
let mut store = StoreContextMut(&mut *raw_store.cast());
321-
let instance = Instance::from_wasmtime(store.0, (*instance).id());
322-
323-
crate::runtime::vm::catch_unwind_and_record_trap(|| {
324-
store.0.call_hook(CallHook::CallingHost)?;
325-
let res = func(store.as_context_mut(), instance, types);
326-
store.0.call_hook(CallHook::ReturningFromHost)?;
327-
res
316+
ComponentInstance::from_vmctx(cx, |store, instance| {
317+
let mut store = store.unchecked_context_mut();
318+
319+
crate::runtime::vm::catch_unwind_and_record_trap(|| {
320+
store.0.call_hook(CallHook::CallingHost)?;
321+
let res = func(store.as_context_mut(), instance);
322+
store.0.call_hook(CallHook::ReturningFromHost)?;
323+
res
324+
})
328325
})
329326
}
330327

331328
unsafe fn call_host_dynamic<T, F>(
332329
mut store: StoreContextMut<'_, T>,
333330
instance: Instance,
334-
types: &Arc<ComponentTypes>,
335331
ty: TypeFuncIndex,
336332
mut flags: InstanceFlags,
337333
memory: *mut VMMemoryDefinition,
@@ -366,10 +362,11 @@ where
366362
let args;
367363
let ret_index;
368364

365+
let types = store[instance.id()].component().types().clone();
369366
let func_ty = &types[ty];
370367
let param_tys = &types[func_ty.params];
371368
let result_tys = &types[func_ty.results];
372-
let mut cx = LiftContext::new(store.0, &options, types, instance);
369+
let mut cx = LiftContext::new(store.0, &options, &types, instance);
373370
cx.enter_call();
374371
if let Some(param_count) = param_tys.abi.flat_count(MAX_FLAT_PARAMS) {
375372
// NB: can use `MaybeUninit::slice_assume_init_ref` when that's stable
@@ -405,7 +402,7 @@ where
405402
closure(store.as_context_mut(), &args, &mut result_vals)?;
406403
flags.set_may_leave(false);
407404

408-
let mut cx = LowerContext::new(store, &options, types, instance);
405+
let mut cx = LowerContext::new(store, &options, &types, instance);
409406
if let Some(cnt) = result_tys.abi.flat_count(MAX_FLAT_RESULTS) {
410407
let mut dst = storage[..cnt].iter_mut();
411408
for (val, ty) in result_vals.iter().zip(result_tys.types.iter()) {
@@ -463,11 +460,10 @@ where
463460
{
464461
let data = data.as_ptr() as *const F;
465462
unsafe {
466-
call_host_and_handle_result(cx, |store, instance, types| {
463+
call_host_and_handle_result(cx, |store, instance| {
467464
call_host_dynamic::<T, _>(
468465
store,
469466
instance,
470-
types,
471467
TypeFuncIndex::from_u32(ty),
472468
InstanceFlags::from_raw(flags),
473469
memory,

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

Lines changed: 98 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,10 @@ use crate::instance::OwnedImports;
99
use crate::linker::DefinitionType;
1010
use crate::prelude::*;
1111
use crate::runtime::vm::VMFuncRef;
12-
use crate::runtime::vm::component::{ComponentInstance, OwnedComponentInstance};
12+
use crate::runtime::vm::component::{
13+
CallContexts, ComponentInstance, OwnedComponentInstance, ResourceTables, TypedResource,
14+
TypedResourceIndex,
15+
};
1316
use crate::store::StoreOpaque;
1417
use crate::{AsContext, AsContextMut, Engine, Module, StoreContextMut};
1518
use alloc::sync::Arc;
@@ -376,6 +379,100 @@ impl Instance {
376379
pub(crate) fn id(&self) -> StoreComponentInstanceId {
377380
self.id
378381
}
382+
383+
/// Implementation of the `resource.new` intrinsic for `i32`
384+
/// representations.
385+
pub(crate) fn resource_new32(
386+
self,
387+
store: &mut StoreOpaque,
388+
ty: TypeResourceTableIndex,
389+
rep: u32,
390+
) -> Result<u32> {
391+
let (calls, _, _, instance) = store.component_resource_state_with_instance(self);
392+
resource_tables(calls, instance).resource_new(TypedResource::Component { ty, rep })
393+
}
394+
395+
/// Implementation of the `resource.rep` intrinsic for `i32`
396+
/// representations.
397+
pub(crate) fn resource_rep32(
398+
self,
399+
store: &mut StoreOpaque,
400+
ty: TypeResourceTableIndex,
401+
index: u32,
402+
) -> Result<u32> {
403+
let (calls, _, _, instance) = store.component_resource_state_with_instance(self);
404+
resource_tables(calls, instance).resource_rep(TypedResourceIndex::Component { ty, index })
405+
}
406+
407+
/// Implementation of the `resource.drop` intrinsic.
408+
pub(crate) fn resource_drop(
409+
self,
410+
store: &mut StoreOpaque,
411+
ty: TypeResourceTableIndex,
412+
index: u32,
413+
) -> Result<Option<u32>> {
414+
let (calls, _, _, instance) = store.component_resource_state_with_instance(self);
415+
resource_tables(calls, instance).resource_drop(TypedResourceIndex::Component { ty, index })
416+
}
417+
418+
pub(crate) fn resource_transfer_own(
419+
self,
420+
store: &mut StoreOpaque,
421+
index: u32,
422+
src: TypeResourceTableIndex,
423+
dst: TypeResourceTableIndex,
424+
) -> Result<u32> {
425+
let (calls, _, _, instance) = store.component_resource_state_with_instance(self);
426+
let mut tables = resource_tables(calls, instance);
427+
let rep = tables.resource_lift_own(TypedResourceIndex::Component { ty: src, index })?;
428+
tables.resource_lower_own(TypedResource::Component { ty: dst, rep })
429+
}
430+
431+
pub(crate) fn resource_transfer_borrow(
432+
self,
433+
store: &mut StoreOpaque,
434+
index: u32,
435+
src: TypeResourceTableIndex,
436+
dst: TypeResourceTableIndex,
437+
) -> Result<u32> {
438+
let dst_owns_resource = store[self.id()].resource_owned_by_own_instance(dst);
439+
let (calls, _, _, instance) = store.component_resource_state_with_instance(self);
440+
let mut tables = resource_tables(calls, instance);
441+
let rep = tables.resource_lift_borrow(TypedResourceIndex::Component { ty: src, index })?;
442+
// Implement `lower_borrow`'s special case here where if a borrow's
443+
// resource type is owned by `dst` then the destination receives the
444+
// representation directly rather than a handle to the representation.
445+
//
446+
// This can perhaps become a different libcall in the future to avoid
447+
// this check at runtime since we know at compile time whether the
448+
// destination type owns the resource, but that's left as a future
449+
// refactoring if truly necessary.
450+
if dst_owns_resource {
451+
return Ok(rep);
452+
}
453+
tables.resource_lower_borrow(TypedResource::Component { ty: dst, rep })
454+
}
455+
456+
pub(crate) fn resource_enter_call(self, store: &mut StoreOpaque) {
457+
let (calls, _, _, instance) = store.component_resource_state_with_instance(self);
458+
resource_tables(calls, instance).enter_call()
459+
}
460+
461+
pub(crate) fn resource_exit_call(self, store: &mut StoreOpaque) -> Result<()> {
462+
let (calls, _, _, instance) = store.component_resource_state_with_instance(self);
463+
resource_tables(calls, instance).exit_call()
464+
}
465+
}
466+
467+
fn resource_tables<'a>(
468+
calls: &'a mut CallContexts,
469+
instance: &'a mut ComponentInstance,
470+
) -> ResourceTables<'a> {
471+
ResourceTables {
472+
host_table: None,
473+
calls,
474+
guest: Some(instance.guest_tables()),
475+
}
379476
}
380477

381478
/// Trait used to lookup the export of a component instance.

crates/wasmtime/src/runtime/externals/global.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -287,7 +287,9 @@ impl Global {
287287
),
288288
#[cfg(feature = "component-model")]
289289
ExportGlobalKind::ComponentFlags(vmctx, index) => (
290-
vm::component::ComponentInstance::from_vmctx(vmctx, |i| i.id().as_u32()),
290+
vm::component::ComponentInstance::from_vmctx(vmctx, |_, i| {
291+
i.id().instance().as_u32()
292+
}),
291293
VMGlobalKind::ComponentFlags(index),
292294
),
293295
};

crates/wasmtime/src/runtime/func.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2048,7 +2048,8 @@ impl<T> Caller<'_, T> {
20482048
R: 'static,
20492049
{
20502050
crate::runtime::vm::InstanceAndStore::from_vmctx(caller, |pair| {
2051-
let (instance, mut store) = pair.unpack_context_mut::<T>();
2051+
let (instance, store) = pair.unpack_mut();
2052+
let mut store = store.unchecked_context_mut::<T>();
20522053
let caller = Instance::from_wasmtime(instance.id(), store.0);
20532054

20542055
let (gc_lifo_scope, ret) = {

crates/wasmtime/src/runtime/vm.rs

Lines changed: 29 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,9 @@ pub(crate) struct f32x4(crate::uninhabited::Uninhabited);
3333
#[derive(Copy, Clone)]
3434
pub(crate) struct f64x2(crate::uninhabited::Uninhabited);
3535

36+
use crate::StoreContextMut;
3637
use crate::prelude::*;
38+
use crate::store::StoreInner;
3739
use crate::store::StoreOpaque;
3840
use alloc::sync::Arc;
3941
use core::fmt;
@@ -166,13 +168,21 @@ cfg_if::cfg_if! {
166168
///
167169
/// This trait is used to store a raw pointer trait object within each
168170
/// `VMContext`. This raw pointer trait object points back to the
169-
/// `wasmtime::Store` internally but is type-erased so this `wasmtime-runtime`
170-
/// crate doesn't need the entire `wasmtime` crate to build.
171+
/// `wasmtime::Store` internally but is type-erased to avoid needing to
172+
/// monomorphize the entire runtime on the `T` in `Store<T>`
171173
///
172-
/// Note that this is an extra-unsafe trait because no heed is paid to the
173-
/// lifetime of this store or the Send/Sync-ness of this store. All of that must
174-
/// be respected by embedders (e.g. the `wasmtime::Store` structure). The theory
175-
/// is that `wasmtime::Store` handles all this correctly.
174+
/// # Safety
175+
///
176+
/// This trait should be implemented by nothing other than `StoreInner<T>` in
177+
/// this crate. It's not sound to implement it for anything else due to
178+
/// `unchecked_context_mut` below.
179+
///
180+
/// It's also worth nothing that there are various locations where a `*mut dyn
181+
/// VMStore` is asserted to be both `Send` and `Sync` which disregards the `T`
182+
/// that's actually stored in the store itself. It's assume that the high-level
183+
/// APIs using `Store<T>` are correctly inferring send/sync on the returned
184+
/// values (e.g. futures) and that internally in the runtime we aren't doing
185+
/// anything "weird" with threads for example.
176186
pub unsafe trait VMStore {
177187
/// Get a shared borrow of this store's `StoreOpaque`.
178188
fn store_opaque(&self) -> &StoreOpaque;
@@ -265,6 +275,19 @@ impl DerefMut for dyn VMStore + '_ {
265275
}
266276
}
267277

278+
impl dyn VMStore + '_ {
279+
/// Asserts that this `VMStore` was originally paired with `StoreInner<T>`
280+
/// and then casts to the `StoreContextMut` type.
281+
///
282+
/// # Unsafety
283+
///
284+
/// This method is not safe as there's no static guarantee that `T` is
285+
/// correct for this store.
286+
pub(crate) unsafe fn unchecked_context_mut<T>(&mut self) -> StoreContextMut<'_, T> {
287+
StoreContextMut(&mut *(self as *mut dyn VMStore as *mut StoreInner<T>))
288+
}
289+
}
290+
268291
/// A newtype wrapper around `NonNull<dyn VMStore>` intended to be a
269292
/// self-pointer back to the `Store<T>` within raw data structures like
270293
/// `VMContext`.

0 commit comments

Comments
 (0)