Skip to content

Commit

Permalink
Add new instance cache: CachedInstance (#1076)
Browse files Browse the repository at this point in the history
* add CachedInstance

* add entity getters to CachedInstance API

* update cache docs and fix doc links

* apply rustfmt

* use Executor::instance where possible

* add CallStack::instance_expect

* fix doc links
  • Loading branch information
Robbepop authored Jun 18, 2024
1 parent f8fd9d1 commit 6820fcc
Show file tree
Hide file tree
Showing 10 changed files with 292 additions and 128 deletions.
255 changes: 214 additions & 41 deletions crates/wasmi/src/engine/executor/cache.rs
Original file line number Diff line number Diff line change
@@ -1,22 +1,86 @@
use crate::{core::UntypedVal, module::DEFAULT_MEMORY_INDEX, store::StoreInner, Instance};
use crate::{
core::UntypedVal,
engine::{
bytecode::{DataSegmentIdx, ElementSegmentIdx, FuncIdx, GlobalIdx, SignatureIdx, TableIdx},
DedupFuncType,
},
instance::InstanceEntity,
memory::DataSegment,
module::DEFAULT_MEMORY_INDEX,
store::StoreInner,
table::ElementSegment,
Func,
Global,
Instance,
Memory,
Table,
};
use core::ptr::{self, NonNull};

/// Cached default linear memory bytes.
#[derive(Debug, Copy, Clone)]
pub struct CachedMemory {
data: NonNull<[u8]>,
/// Cached WebAssembly instance.
#[derive(Debug)]
pub struct CachedInstance {
/// The currently used instance.
instance: NonNull<InstanceEntity>,
/// The cached bytes of the default linear memory.
pub memory: CachedMemory,
/// The cached value of the global variable at index 0.
pub global: CachedGlobal,
}

impl Default for CachedMemory {
impl CachedInstance {
/// Creates a new [`CachedInstance`].
#[inline]
fn default() -> Self {
pub fn new(ctx: &mut StoreInner, instance: &Instance) -> Self {
let (instance, memory, global) = Self::load_caches(ctx, instance);
Self {
data: NonNull::from(&mut []),
instance,
memory,
global,
}
}
}

impl CachedMemory {
/// Loads the [`InstanceEntity`] from the [`StoreInner`].
#[inline]
fn load_instance<'ctx>(ctx: &'ctx mut StoreInner, instance: &Instance) -> &'ctx InstanceEntity {
ctx.resolve_instance(instance)
}

/// Loads the cached global and linear memory.
#[inline]
fn load_caches(
ctx: &mut StoreInner,
instance: &Instance,
) -> (NonNull<InstanceEntity>, CachedMemory, CachedGlobal) {
let entity = Self::load_instance(ctx, instance);
let memory = entity.get_memory(DEFAULT_MEMORY_INDEX);
let global = entity.get_global(0);
let instance = entity.into();
let memory = memory
.map(|memory| CachedMemory::new(ctx, &memory))
.unwrap_or_default();
let global = global
.map(|global| CachedGlobal::new(ctx, &global))
.unwrap_or_default();
(instance, memory, global)
}

/// Update the cached instance, linear memory and global variable.
#[inline]
pub fn update(&mut self, ctx: &mut StoreInner, instance: &Instance) {
(self.instance, self.memory, self.global) = Self::load_caches(ctx, instance);
}

/// Returns a shared reference to the cached [`InstanceEntity`].
///
/// # Safety
///
/// It is the callers responsibility to use this method only when the caches are fresh.
#[inline]
unsafe fn as_ref(&self) -> &InstanceEntity {
unsafe { self.instance.as_ref() }
}

/// Updates the [`CachedMemory`]'s linear memory data pointer.
///
/// # Note
Expand All @@ -29,42 +93,160 @@ impl CachedMemory {
/// - successfully growing the default linear memory
/// - calling functions defined in other instances via imported or indirect calls
/// - returning from functions that changed the currently used instance
///
/// # Safety
///
/// It is the callers responsibility to use this method only when the caches are fresh.
#[inline]
pub fn update(&mut self, ctx: &mut StoreInner, instance: &Instance) {
self.data = Self::load_default_memory(ctx, instance);
pub unsafe fn update_memory(&mut self, ctx: &mut StoreInner) {
let instance = unsafe { self.as_ref() };
self.memory = instance
.get_memory(DEFAULT_MEMORY_INDEX)
.map(|memory| CachedMemory::new(ctx, &memory))
.unwrap_or_default();
}

/// Returns the [`Func`] at the `index` if any.
///
/// # Safety
///
/// It is the callers responsibility to use this method only when the caches are fresh.
#[inline]
pub unsafe fn get_func(&self, index: FuncIdx) -> Option<Func> {
let instance = unsafe { self.as_ref() };
instance.get_func(u32::from(index))
}

/// Returns the [`Memory`] at the `index` if any.
///
/// # Safety
///
/// It is the callers responsibility to use this method only when the caches are fresh.
#[inline]
pub unsafe fn get_memory(&self, index: u32) -> Option<Memory> {
let instance = unsafe { self.as_ref() };
instance.get_memory(index)
}

/// Returns the [`Table`] at the `index` if any.
///
/// # Safety
///
/// It is the callers responsibility to use this method only when the caches are fresh.
#[inline]
pub unsafe fn get_table(&self, index: TableIdx) -> Option<Table> {
let instance = unsafe { self.as_ref() };
instance.get_table(u32::from(index))
}

/// Returns the [`Global`] at the `index` if any.
///
/// # Safety
///
/// It is the callers responsibility to use this method only when the caches are fresh.
#[inline]
pub unsafe fn get_global(&self, index: GlobalIdx) -> Option<Global> {
let instance = unsafe { self.as_ref() };
instance.get_global(u32::from(index))
}

/// Returns the [`DataSegment`] at the `index` if any.
///
/// # Safety
///
/// It is the callers responsibility to use this method only when the caches are fresh.
#[inline]
pub unsafe fn get_data_segment(&self, index: DataSegmentIdx) -> Option<DataSegment> {
let instance = unsafe { self.as_ref() };
instance.get_data_segment(u32::from(index))
}

/// Returns the [`ElementSegment`] at the `index` if any.
///
/// # Safety
///
/// It is the callers responsibility to use this method only when the caches are fresh.
#[inline]
pub unsafe fn get_element_segment(&self, index: ElementSegmentIdx) -> Option<ElementSegment> {
let instance = unsafe { self.as_ref() };
instance.get_element_segment(u32::from(index))
}

/// Returns the [`DedupFuncType`] at the `index` if any.
///
/// # Safety
///
/// It is the callers responsibility to use this method only when the caches are fresh.
#[inline]
pub unsafe fn get_func_type_dedup(&self, index: SignatureIdx) -> Option<DedupFuncType> {
let instance = unsafe { self.as_ref() };
instance.get_signature(u32::from(index)).copied()
}
}

/// Cached default linear memory bytes.
#[derive(Debug)]
pub struct CachedMemory {
data: NonNull<[u8]>,
}

impl Default for CachedMemory {
#[inline]
fn default() -> Self {
Self {
data: NonNull::from(&mut []),
}
}
}

impl CachedMemory {
/// Create a new [`CachedMemory`].
#[inline]
fn new(ctx: &mut StoreInner, instance: &Memory) -> Self {
let data = Self::load_default_memory(ctx, instance);
Self { data }
}

/// Loads the default [`Memory`] of the currently used [`Instance`].
///
/// # Note
///
/// Must be called whenever the heap allocation of the [`CachedMemory`]
/// could have been changed and thus the cached pointer invalidated.
///
/// # Panics
///
/// If the currently used [`Instance`] does not have a default linear memory.
///
/// [`Memory`]: crate::Memory
#[inline]
fn load_default_memory(ctx: &mut StoreInner, instance: &Instance) -> NonNull<[u8]> {
ctx.resolve_instance(instance)
.get_memory(DEFAULT_MEMORY_INDEX)
.map(|memory| ctx.resolve_memory_mut(&memory).data_mut())
.unwrap_or_else(|| &mut [])
.into()
fn load_default_memory(ctx: &mut StoreInner, memory: &Memory) -> NonNull<[u8]> {
ctx.resolve_memory_mut(memory).data_mut().into()
}

/// Returns a shared slice to the bytes of the cached default linear memory.
///
/// # Safety
///
/// The user is required to call [`CachedMemory::load_default_memory`] according to its specification.
#[inline]
pub unsafe fn data(&self) -> &[u8] {
unsafe { self.data.as_ref() }
}

/// Returns an exclusive slice to the bytes of the cached default linear memory.
///
/// # Safety
///
/// The user is required to call [`CachedMemory::load_default_memory`] according to its specification.
#[inline]
pub unsafe fn data_mut(&mut self) -> &mut [u8] {
unsafe { self.data.as_mut() }
}
}

/// Cached default global variable value.
#[derive(Debug, Copy, Clone)]
#[derive(Debug)]
pub struct CachedGlobal {
data: NonNull<UntypedVal>,
}
Expand Down Expand Up @@ -92,43 +274,35 @@ static mut FALLBACK_GLOBAL_VALUE: NonNull<UntypedVal> = {
};

impl CachedGlobal {
/// Updates the [`CachedGlobal`]'s data pointer.
///
/// # Note
///
/// This needs to be called whenever the cached pointer might have changed.
///
/// The global variable pointer might change when ...
///
/// - calling a host function
/// - calling functions defined in other instances via imported or indirect calls
/// - returning from functions that changed the currently used instance
/// Create a new [`CachedGlobal`].
#[inline]
pub fn update(&mut self, ctx: &mut StoreInner, instance: &Instance) {
self.data = Self::load_global(ctx, instance);
fn new(ctx: &mut StoreInner, global: &Global) -> Self {
let data = Self::load_global(ctx, global);
Self { data }
}

/// Loads the default [`Global`] of the currently used [`Instance`].
///
/// # Note
///
/// Must be called whenever the heap allocation of the [`CachedGlobal`]
/// could have been changed and thus the cached pointer invalidated.
///
/// # Panics
///
/// If the currently used [`Instance`] does not have a default linear memory.
///
/// [`Global`]: crate::Global
#[inline]
fn load_global(ctx: &mut StoreInner, instance: &Instance) -> NonNull<UntypedVal> {
ctx.resolve_instance(instance)
.get_global(0)
.map(|global| ctx.resolve_global_mut(&global).get_untyped_ptr())
.unwrap_or_else(|| unsafe { FALLBACK_GLOBAL_VALUE })
fn load_global(ctx: &mut StoreInner, global: &Global) -> NonNull<UntypedVal> {
ctx.resolve_global_mut(global).get_untyped_ptr()
}

/// Returns the value of the cached global variable.
///
/// # Safety
///
/// The user is required to call [`CachedGlobal::update`] according to its specification.
/// For more information read the docs of [`CachedGlobal::update`].
/// The user is required to call [`CachedGlobal::load_global`] according to its specification.
#[inline]
pub unsafe fn get(&self) -> UntypedVal {
// SAFETY: This API guarantees to always write to a valid pointer
Expand All @@ -140,8 +314,7 @@ impl CachedGlobal {
///
/// # Safety
///
/// The user is required to call [`CachedGlobal::update`] according to its specification.
/// For more information read the docs of [`CachedGlobal::update`].
/// The user is required to call [`CachedGlobal::load_global`] according to its specification.
#[inline]
pub unsafe fn set(&mut self, new_value: UntypedVal) {
// SAFETY: This API guarantees to always write to a valid pointer
Expand Down
Loading

0 comments on commit 6820fcc

Please sign in to comment.