Skip to content

Commit 96ca30c

Browse files
authored
Make InstanceEntity dynamically sized to drop one indirection in the executor (#2005)
* add HandleAndCache::cast_* methods * use new HandleAndCache::cast_* methods * make HandleAndCache::handle private * turn InstanceEntity into dynamically sized type * adjust StoreInner to store Box<InstanceEntity> * update Handle trait to allow ?Sized Entity * relax const assert * add ThinPtr utility type * rename InstanceHeader -> InstanceEntityHeader also make it private * make all InstanceEntityHeader methods private * add ThinPtr<InstanceEntity> API * add missing HANDLES_OFFSET const for new API * use ThinPtr<InstanceEntity> in the executor * remove no longer needed InstanceEntity APIs * introduce typed CacheAndEntity<T> type * adjust executor usage * remove unnecessary InstanceHandle trait again * rename HandleAndCache -> AnyHandleAndEntity * rename ThinPtr<InstanceEntity> APIs * return Option from ThinPtr<InstanceEntity>::entry APIs * use names from std for ThinPtr API * add validity doc section to Inst * re-design ThinPtr::as_byte_ptr -> cast<U> * fix some minor doc issues * improve InstanceEntity[Header] docs * add AnyHandle::assert_kind * re-design AnyHandleAndEntity conversions to typed wrapper * elaborate comment in InstanceEntity::alloc * use new typed conversion APIs in * update API impl of ThinPtr<InstanceEntity> * apply improvements to InstanceEntity::alloc from code review * remove unused imports * add new instantiation tests * add new test to guard dangling Inst pointer * remove superflous comma * move ptr.rs -> instance/thin_ptr.rs * elaborate on the safety comment for a Sync impl * use crate imports * add cfg-guards for release builds * use public APIs in tests * move InstanceEntity defs/impls into entity.rs submodule * clean-ups for StoreInner * improve HANDLES_OFFSET computation * add another const assert * use HANDLES_OFFSET to verify Layout usage * carve out manual handles_offset checker * remove ExactSizeIterator bounds from alloc * clean-up alloc method
1 parent 5bd5081 commit 96ca30c

14 files changed

Lines changed: 1151 additions & 447 deletions

File tree

crates/wasmi/src/engine/executor/handler/state.rs

Lines changed: 40 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -23,19 +23,12 @@ use crate::{
2323
},
2424
utils::unreachable_unchecked,
2525
},
26-
instance::InstanceEntity,
26+
instance::{InstanceEntity, ThinPtr},
2727
ir::{self, BoundedSlotSpan, Slot, SlotSpan},
2828
store::PrunedStore,
2929
};
3030
use alloc::vec::Vec;
31-
use core::{
32-
cmp,
33-
marker::PhantomData,
34-
mem,
35-
ops,
36-
ptr::{self, NonNull},
37-
slice,
38-
};
31+
use core::{cmp, marker::PhantomData, mem, ops, ptr, slice};
3932

4033
pub struct VmState<'vm> {
4134
pub store: &'vm mut PrunedStore,
@@ -151,6 +144,22 @@ impl DoneReason {
151144
}
152145

153146
/// A thin-wrapper around a non-owned [`InstanceEntity`].
147+
///
148+
/// # Validity
149+
///
150+
/// Every `Inst` is created from a live [`InstanceEntity`] that the [`Store`] currently owns,
151+
/// and the executor keeps that instance alive and warmed up for as long as the `Inst` is
152+
/// reachable — it is stored in [`Args`] and in the [`CallStack`] frames of the very execution
153+
/// that owns the [`Store`]. Since the entity is boxed, allocating further instances (e.g. from
154+
/// a host call) does not move it.
155+
///
156+
/// The `unsafe` methods of [`ThinPtr<InstanceEntity>`] therefore hold for any `Inst` reached
157+
/// through [`Inst::as_ptr`], and call sites need only justify the *kind* of the address they
158+
/// pass, not the liveness of the instance.
159+
///
160+
/// [`Store`]: crate::Store
161+
/// [`Args`]: super::Args
162+
/// [`ThinPtr<InstanceEntity>`]: ThinPtr
154163
#[derive(Debug, Copy, Clone)]
155164
#[repr(transparent)]
156165
pub struct Inst {
@@ -166,7 +175,7 @@ pub struct Inst {
166175
/// integer and float won't be a terrible trade-off.
167176
value: InstRepr,
168177
/// Marks `Inst` as logically containing a shared raw pointer.
169-
marker: PhantomData<*const InstanceEntity>,
178+
marker: PhantomData<ThinPtr<InstanceEntity>>,
170179
}
171180

172181
/// The underlying float representation of `Inst` for 64-bit platforms.
@@ -185,60 +194,44 @@ const _: () = {
185194
impl From<&'_ InstanceEntity> for Inst {
186195
#[inline]
187196
fn from(entity: &'_ InstanceEntity) -> Self {
188-
let addr = (entity as *const InstanceEntity).expose_provenance();
189-
Self::from_addr(addr)
197+
Self::from(ThinPtr::from_ref(entity))
190198
}
191199
}
192200

193-
impl From<NonNull<InstanceEntity>> for Inst {
201+
impl From<ThinPtr<InstanceEntity>> for Inst {
194202
#[inline]
195-
fn from(entity: NonNull<InstanceEntity>) -> Self {
196-
let addr = entity.as_ptr().expose_provenance();
197-
Self::from_addr(addr)
203+
fn from(entity: ThinPtr<InstanceEntity>) -> Self {
204+
let value = InstRepr::from_ne_bytes(entity.expose_provenance().to_ne_bytes());
205+
Self {
206+
value,
207+
marker: PhantomData,
208+
}
198209
}
199210
}
200211

201212
impl PartialEq for Inst {
202213
#[inline]
203214
fn eq(&self, other: &Self) -> bool {
204-
self.as_ptr().addr() == other.as_ptr().addr()
215+
self.as_ptr() == other.as_ptr()
205216
}
206217
}
207218
impl Eq for Inst {}
208219

209220
impl Inst {
210-
/// Creates a new [`Inst`] from the given `addr` value.
211-
#[inline]
212-
fn from_addr(addr: usize) -> Self {
213-
let value = InstRepr::from_ne_bytes(addr.to_ne_bytes());
214-
Self {
215-
value,
216-
marker: PhantomData,
217-
}
218-
}
219-
220-
/// Converts the underlying representation back into its original pointer value.
221-
#[inline]
222-
pub fn as_ptr(&self) -> NonNull<InstanceEntity> {
223-
let bits = usize::from_ne_bytes(self.value.to_ne_bytes());
224-
let ptr = ptr::with_exposed_provenance::<InstanceEntity>(bits);
225-
unsafe { NonNull::new_unchecked(ptr as *mut InstanceEntity) }
226-
}
227-
228-
/// Returns a shared reference to the referenced [`InstanceEntity`].
221+
/// Returns the thin pointer to the referenced [`InstanceEntity`].
229222
///
230-
/// # Safety
223+
/// # Note
231224
///
232-
/// The caller must ensure that:
225+
/// This cannot be a `NonNull<InstanceEntity>` since [`InstanceEntity`] is dynamically
226+
/// sized. Its thin-pointer API is provided by [`ThinPtr<InstanceEntity>`].
233227
///
234-
/// - The [`Inst`] was constructed from a valid, properly aligned
235-
/// `InstanceEntity` pointer.
236-
/// - The referenced [`InstanceEntity`] remains alive and is not
237-
/// mutably accessed for the entire duration of the returned
238-
/// reference.
228+
/// [`ThinPtr<InstanceEntity>`]: ThinPtr
239229
#[inline]
240-
pub unsafe fn as_ref(&self) -> &InstanceEntity {
241-
unsafe { self.as_ptr().as_ref() }
230+
pub fn as_ptr(&self) -> ThinPtr<InstanceEntity> {
231+
let addr = usize::from_ne_bytes(self.value.to_ne_bytes());
232+
// Safety: `Inst` can only ever be created from a `ThinPtr<InstanceEntity>` whose
233+
// provenance has been exposed.
234+
unsafe { ThinPtr::with_exposed_provenance(addr) }
242235
}
243236
}
244237

@@ -269,8 +262,8 @@ mod inst_tests {
269262
use super::*;
270263

271264
const _: fn() = || {
272-
fn assert_send<T: Send>() {}
273-
fn assert_sync<T: Sync>() {}
265+
fn assert_send<T: ?Sized + Send>() {}
266+
fn assert_sync<T: ?Sized + Sync>() {}
274267

275268
assert_send::<InstanceEntity>();
276269
assert_sync::<InstanceEntity>();

crates/wasmi/src/engine/executor/handler/utils.rs

Lines changed: 53 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -539,22 +539,26 @@ pub fn exec_copy_span_des(sp: Sp, dst: SlotSpan, src: SlotSpan, len: u16) {
539539
/// so the default memory's address is not guaranteed to be zero.
540540
#[inline]
541541
pub fn is_default_memory(instance: Inst, memory: ir::MemoryAddr) -> bool {
542-
let instance = unsafe { instance.as_ref() };
542+
// SAFETY: `instance` refers to a live instance for the duration of the call.
543+
let layout = unsafe { instance.as_ptr().layout() };
543544
matches!(
544-
instance.layout().memory_addr(0),
545+
layout.memory_addr(0),
545546
Some(addr) if u32::from(addr) == u32::from(memory)
546547
)
547548
}
548549

549550
pub fn extract_mem0(_store: &mut PrunedStore, inst: Inst) -> (Mem0Ptr, Mem0Len) {
550-
let instance = unsafe { inst.as_ref() };
551-
let Some(addr) = instance.layout().memory_addr(0) else {
551+
// SAFETY: `inst` refers to a live instance for the duration of the call.
552+
let Some(addr) = (unsafe { inst.as_ptr().layout() }).memory_addr(0) else {
552553
return (Mem0Ptr::from([].as_mut_ptr()), Mem0Len::from(0));
553554
};
554-
let Some(mem0) = instance.get_memory_ptr(addr) else {
555-
unsafe { unreachable_unchecked!() }
555+
// SAFETY: `addr` stems from the instance layout and thus addresses a memory entry
556+
// whose cache was warmed at instantiation.
557+
let Some(mem0) = (unsafe { inst.as_ptr().get_memory(addr) }) else {
558+
unsafe { unreachable_unchecked!("missing memory at: {addr:?}") }
556559
};
557560
// SAFETY: warmed at instantiation; the `_store` borrow scopes exclusive memory access.
561+
let mem0 = mem0.entity();
558562
let mem0 = unsafe { &mut *mem0.as_ptr() }.data_mut();
559563
let mem0_ptr = mem0.as_mut_ptr();
560564
let mem0_len = mem0.len();
@@ -583,7 +587,7 @@ pub fn memory_slice_mut(
583587

584588
macro_rules! impl_resolve_from_instance {
585589
(
586-
$( fn $fn:ident(inst: Inst, store: &mut StoreInner, $param:ident: ir::$param_ty:ident) -> &mut $ret:ty = $getter:expr );* $(;)?
590+
$( fn $fn:ident(inst: Inst, store: &mut StoreInner, $param:ident: ir::$param_ty:ident) -> &mut $ret:ty = $entry:ident );* $(;)?
587591
) => {
588592
$(
589593
/// Resolves the entity from the warmed up instance cache.
@@ -592,10 +596,9 @@ macro_rules! impl_resolve_from_instance {
592596
/// cannot alias a concurrent store mutation (this is the safe wrapper).
593597
#[inline]
594598
pub fn $fn(inst: Inst, _store: &mut StoreInner, $param: ir::$param_ty) -> &mut $ret {
595-
let inst = unsafe { inst.as_ref() };
596-
let raw_addr = ::core::primitive::u32::from($param);
597-
let addr = <$param_ty>::from(raw_addr);
598-
let Some(ptr) = $getter(inst, addr) else {
599+
let addr = <$param_ty>::from(::core::primitive::u32::from($param));
600+
// SAFETY: `addr` addresses an entry of this kind by translation invariant.
601+
let Some(entry) = (unsafe { inst.as_ptr().$entry(addr) }) else {
599602
unsafe {
600603
$crate::engine::utils::unreachable_unchecked!(
601604
::core::concat!("missing ", ::core::stringify!($param), " at: {:?}"),
@@ -604,22 +607,22 @@ macro_rules! impl_resolve_from_instance {
604607
}
605608
};
606609
// SAFETY: warmed at instantiation; the `_store` borrow scopes the reference.
607-
unsafe { &mut *ptr.as_ptr() }
610+
unsafe { &mut *entry.entity().as_ptr() }
608611
}
609612
)*
610613
};
611614
}
612615
impl_resolve_from_instance! {
613-
fn load_data(inst: Inst, store: &mut StoreInner, data: ir::DataAddr) -> &mut DataSegmentEntity = InstanceEntity::get_data_ptr;
614-
fn load_elem(inst: Inst, store: &mut StoreInner, elem: ir::ElemAddr) -> &mut ElementSegmentEntity = InstanceEntity::get_elem_ptr;
615-
fn load_global(inst: Inst, store: &mut StoreInner, global: ir::GlobalAddr) -> &mut GlobalEntity = InstanceEntity::get_global_ptr;
616-
fn load_memory(inst: Inst, store: &mut StoreInner, memory: ir::MemoryAddr) -> &mut MemoryEntity = InstanceEntity::get_memory_ptr;
617-
fn load_table(inst: Inst, store: &mut StoreInner, table: ir::TableAddr) -> &mut TableEntity = InstanceEntity::get_table_ptr;
616+
fn load_data(inst: Inst, store: &mut StoreInner, data: ir::DataAddr) -> &mut DataSegmentEntity = get_data;
617+
fn load_elem(inst: Inst, store: &mut StoreInner, elem: ir::ElemAddr) -> &mut ElementSegmentEntity = get_elem;
618+
fn load_global(inst: Inst, store: &mut StoreInner, global: ir::GlobalAddr) -> &mut GlobalEntity = get_global;
619+
fn load_memory(inst: Inst, store: &mut StoreInner, memory: ir::MemoryAddr) -> &mut MemoryEntity = get_memory;
620+
fn load_table(inst: Inst, store: &mut StoreInner, table: ir::TableAddr) -> &mut TableEntity = get_table;
618621
}
619622

620623
macro_rules! impl_resolve_ptr_from_instance {
621624
(
622-
$( fn $fn:ident(inst: Inst, $param:ident: ir::$param_ty:ident) -> $ret:ty = $getter:expr );* $(;)?
625+
$( fn $fn:ident(inst: Inst, $param:ident: ir::$param_ty:ident) -> $ret:ty = $entry:ident );* $(;)?
623626
) => {
624627
$(
625628
/// Resolves a pointer to the entity from the warmed up instance cache.
@@ -637,27 +640,26 @@ macro_rules! impl_resolve_ptr_from_instance {
637640
/// ensure the resulting reference does not alias any other live reference.
638641
#[inline]
639642
pub unsafe fn $fn(inst: Inst, $param: ir::$param_ty) -> NonNull<$ret> {
640-
let inst = unsafe { inst.as_ref() };
641-
let raw_addr = ::core::primitive::u32::from($param);
642-
let addr = <$param_ty>::from(raw_addr);
643-
let Some(ptr) = $getter(inst, addr) else {
643+
let addr = <$param_ty>::from(::core::primitive::u32::from($param));
644+
// Safety: guaranteed by the caller.
645+
let Some(entry) = (unsafe { inst.as_ptr().$entry(addr) }) else {
644646
unsafe {
645647
$crate::engine::utils::unreachable_unchecked!(
646648
::core::concat!("missing ", ::core::stringify!($param), " at: {:?}"),
647649
addr,
648650
)
649651
}
650652
};
651-
ptr
653+
entry.entity()
652654
}
653655
)*
654656
};
655657
}
656658
impl_resolve_ptr_from_instance! {
657-
fn load_memory_ptr(inst: Inst, memory: ir::MemoryAddr) -> MemoryEntity = InstanceEntity::get_memory_ptr;
658-
fn load_table_ptr(inst: Inst, table: ir::TableAddr) -> TableEntity = InstanceEntity::get_table_ptr;
659-
fn load_data_ptr(inst: Inst, data: ir::DataAddr) -> DataSegmentEntity = InstanceEntity::get_data_ptr;
660-
fn load_elem_ptr(inst: Inst, elem: ir::ElemAddr) -> ElementSegmentEntity = InstanceEntity::get_elem_ptr;
659+
fn load_memory_ptr(inst: Inst, memory: ir::MemoryAddr) -> MemoryEntity = get_memory;
660+
fn load_table_ptr(inst: Inst, table: ir::TableAddr) -> TableEntity = get_table;
661+
fn load_data_ptr(inst: Inst, data: ir::DataAddr) -> DataSegmentEntity = get_data;
662+
fn load_elem_ptr(inst: Inst, elem: ir::ElemAddr) -> ElementSegmentEntity = get_elem;
661663
}
662664

663665
/// Resolves the [`Func`] handle and its warmed up cached [`FuncEntity`] pointer from `inst`.
@@ -666,43 +668,51 @@ impl_resolve_ptr_from_instance! {
666668
/// and must not be turned into a reference that aliases a concurrent store mutation.
667669
#[inline]
668670
pub fn load_func_entry(inst: Inst, func: ir::FuncAddr) -> (Func, NonNull<FuncEntity>) {
669-
let instance = unsafe { inst.as_ref() };
670671
let addr = FuncAddr::from(u32::from(func));
671-
let Some(func_entry) = instance.get_func_entry(addr) else {
672-
unsafe { unreachable_unchecked!("missing func at: {:?}", addr) }
672+
// SAFETY: `addr` addresses a func entry by translation invariant and its cache was
673+
// warmed at instantiation.
674+
let Some(entry) = (unsafe { inst.as_ptr().get_func(addr) }) else {
675+
unsafe { unreachable_unchecked!("missing func at: {addr:?}") }
673676
};
674-
func_entry
677+
(entry.handle(), entry.entity())
675678
}
676679

677680
macro_rules! impl_fetch_from_instance {
678681
(
679-
$( fn $fn:ident($param:ident: $ty:ty as $addr:ty) -> $ret:ty = $getter:expr );* $(;)?
682+
$( fn $fn:ident($param:ident: ir::$param_ty:ident) -> $ret:ty = $entry:ident );* $(;)?
680683
) => {
681684
$(
682-
pub fn $fn(instance: Inst, $param: $ty) -> $ret {
683-
let instance = unsafe { instance.as_ref() };
684-
let raw_addr = ::core::primitive::u32::from($param);
685-
let addr = <$addr>::from(raw_addr);
686-
let Some($param) = $getter(instance, addr) else {
685+
pub fn $fn(instance: Inst, $param: ir::$param_ty) -> $ret {
686+
let addr = <$param_ty>::from(::core::primitive::u32::from($param));
687+
// SAFETY: `addr` addresses an entry of this kind by translation invariant.
688+
let Some(entry) = (unsafe { instance.as_ptr().$entry(addr) }) else {
687689
unsafe {
688690
$crate::engine::utils::unreachable_unchecked!(
689691
::core::concat!("missing ", ::core::stringify!($param), " at: {:?}"),
690692
addr,
691693
)
692694
}
693695
};
694-
$param
696+
entry.handle()
695697
}
696698
)*
697699
};
698700
}
699701
impl_fetch_from_instance! {
700-
fn fetch_func(func: ir::FuncAddr as FuncAddr) -> Func = InstanceEntity::get_func;
701-
fn fetch_memory(memory: ir::MemoryAddr as MemoryAddr) -> Memory = InstanceEntity::get_memory;
702-
fn fetch_table(table: ir::TableAddr as TableAddr) -> Table = InstanceEntity::get_table;
703-
fn fetch_func_type(func_type: ir::FuncType as u32) -> DedupFuncType = {
704-
|instance: &InstanceEntity, index: u32| instance.get_signature(index).copied()
702+
fn fetch_func(func: ir::FuncAddr) -> Func = get_func;
703+
fn fetch_memory(memory: ir::MemoryAddr) -> Memory = get_memory;
704+
fn fetch_table(table: ir::TableAddr) -> Table = get_table;
705+
}
706+
707+
/// Fetches the [`DedupFuncType`] at `func_type` from the instance's function types.
708+
#[inline]
709+
pub fn fetch_func_type(instance: Inst, func_type: ir::FuncType) -> DedupFuncType {
710+
let index = u32::from(func_type);
711+
// SAFETY: `instance` refers to a live instance for the duration of the call.
712+
let Some(func_type) = (unsafe { instance.as_ptr().get_signature(index) }) else {
713+
unsafe { unreachable_unchecked!("missing func type at: {index}") }
705714
};
715+
*func_type
706716
}
707717

708718
macro_rules! impl_resolve_from_store {

crates/wasmi/src/func/caller.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ impl<'a, T> Caller<'a, T> {
3737
let Some(instance) = &self.instance else {
3838
return None;
3939
};
40-
let instance = unsafe { instance.as_ref() };
40+
let instance = unsafe { instance.as_ptr().as_ref() };
4141
instance.get_export(name)
4242
}
4343

crates/wasmi/src/handle.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,10 @@ pub trait Handle: Copy {
66
/// The raw representation of the handle.
77
type Raw: ArenaKey;
88
/// The store owned entity type of the handle.
9-
type Entity;
9+
///
10+
/// This may be unsized: [`InstanceEntity`](crate::InstanceEntity) is a dynamically
11+
/// sized type and thus owned as a `Box<InstanceEntity>` by its arena.
12+
type Entity: ?Sized;
1013
/// The owner associating reference.
1114
type Owned<T>;
1215

0 commit comments

Comments
 (0)