mirror of
https://github.com/mii443/wasmer.git
synced 2025-12-07 05:08:19 +00:00
Implemented shared memory for Wasmer in preparation for multithreading
Fixed linter Fixed clippy Cleaned up some merge leftover
This commit is contained in:
committed by
ptitSeb
parent
0928629051
commit
a5f641b4b0
@@ -73,7 +73,6 @@ pub use crate::js::value::Value as Val;
|
|||||||
|
|
||||||
pub mod vm {
|
pub mod vm {
|
||||||
//! The `vm` module re-exports wasmer-vm types.
|
//! The `vm` module re-exports wasmer-vm types.
|
||||||
|
|
||||||
pub use crate::js::export::VMMemory;
|
pub use crate::js::export::VMMemory;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
//! The import module contains the implementation data structures and helper functions used to
|
//! The import module contains the implementation data structures and helper functions used to
|
||||||
//! manipulate and access a wasm module's imports including memories, tables, globals, and
|
//! manipulate and access a wasm module's imports including memories, tables, globals, and
|
||||||
//! functions.
|
//! functions.
|
||||||
use crate::{Exports, Extern, Module};
|
use crate::{AsStoreMut, Exports, Extern, Memory, Module};
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
use wasmer_compiler::LinkError;
|
use wasmer_compiler::LinkError;
|
||||||
use wasmer_types::ImportError;
|
use wasmer_types::ImportError;
|
||||||
|
use wasmer_vm::VMSharedMemory;
|
||||||
|
|
||||||
/// All of the import data used when instantiating.
|
/// All of the import data used when instantiating.
|
||||||
///
|
///
|
||||||
@@ -111,6 +112,36 @@ impl Imports {
|
|||||||
.insert((ns.to_string(), name.to_string()), val.into());
|
.insert((ns.to_string(), name.to_string()), val.into());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Imports (any) shared memory into the imports.
|
||||||
|
/// (if the module does not import memory then this function is ignored)
|
||||||
|
pub fn import_shared_memory(
|
||||||
|
&mut self,
|
||||||
|
module: &Module,
|
||||||
|
store: &mut impl AsStoreMut,
|
||||||
|
) -> Option<VMSharedMemory> {
|
||||||
|
// Determine if shared memory needs to be created and imported
|
||||||
|
let shared_memory = module
|
||||||
|
.imports()
|
||||||
|
.memories()
|
||||||
|
.next()
|
||||||
|
.map(|a| *a.ty())
|
||||||
|
.map(|ty| {
|
||||||
|
let style = store.as_store_ref().tunables().memory_style(&ty);
|
||||||
|
VMSharedMemory::new(&ty, &style).unwrap()
|
||||||
|
});
|
||||||
|
|
||||||
|
if let Some(memory) = shared_memory {
|
||||||
|
self.define(
|
||||||
|
"env",
|
||||||
|
"memory",
|
||||||
|
Memory::new_from_existing(store, memory.clone().into()),
|
||||||
|
);
|
||||||
|
Some(memory)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Returns the contents of a namespace as an `Exports`.
|
/// Returns the contents of a namespace as an `Exports`.
|
||||||
///
|
///
|
||||||
/// Returns `None` if the namespace doesn't exist.
|
/// Returns `None` if the namespace doesn't exist.
|
||||||
|
|||||||
@@ -57,8 +57,8 @@ pub mod vm {
|
|||||||
//! The `vm` module re-exports wasmer-vm types.
|
//! The `vm` module re-exports wasmer-vm types.
|
||||||
|
|
||||||
pub use wasmer_vm::{
|
pub use wasmer_vm::{
|
||||||
MemoryError, MemoryStyle, TableStyle, VMExtern, VMMemory, VMMemoryDefinition, VMTable,
|
MemoryError, MemoryStyle, TableStyle, VMExtern, VMMemory, VMMemoryDefinition,
|
||||||
VMTableDefinition,
|
VMOwnedMemory, VMSharedMemory, VMTable, VMTableDefinition,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -104,7 +104,8 @@ impl Wasi {
|
|||||||
is_wasix_module(module),
|
is_wasix_module(module),
|
||||||
std::sync::atomic::Ordering::Release,
|
std::sync::atomic::Ordering::Release,
|
||||||
);
|
);
|
||||||
let import_object = import_object_for_all_wasi_versions(store, &wasi_env.env);
|
let mut import_object = import_object_for_all_wasi_versions(store, &wasi_env.env);
|
||||||
|
import_object.import_shared_memory(module, store);
|
||||||
let instance = Instance::new(store, module, &import_object)?;
|
let instance = Instance::new(store, module, &import_object)?;
|
||||||
let memory = instance.exports.get_memory("memory")?;
|
let memory = instance.exports.get_memory("memory")?;
|
||||||
wasi_env.data_mut(store).set_memory(memory.clone());
|
wasi_env.data_mut(store).set_memory(memory.clone());
|
||||||
|
|||||||
@@ -1063,26 +1063,48 @@ pub fn translate_operator<FE: FuncEnvironment + ?Sized>(
|
|||||||
assert!(builder.func.dfg.value_type(expected) == implied_ty);
|
assert!(builder.func.dfg.value_type(expected) == implied_ty);
|
||||||
// `fn translate_atomic_wait` can inspect the type of `expected` to figure out what
|
// `fn translate_atomic_wait` can inspect the type of `expected` to figure out what
|
||||||
// code it needs to generate, if it wants.
|
// code it needs to generate, if it wants.
|
||||||
let res = environ.translate_atomic_wait(
|
match environ.translate_atomic_wait(
|
||||||
builder.cursor(),
|
builder.cursor(),
|
||||||
heap_index,
|
heap_index,
|
||||||
heap,
|
heap,
|
||||||
addr,
|
addr,
|
||||||
expected,
|
expected,
|
||||||
timeout,
|
timeout,
|
||||||
)?;
|
) {
|
||||||
|
Ok(res) => {
|
||||||
state.push1(res);
|
state.push1(res);
|
||||||
}
|
}
|
||||||
|
Err(wasmer_types::WasmError::Unsupported(_err)) => {
|
||||||
|
// If multiple threads hit a mutex then the function will fail
|
||||||
|
builder.ins().trap(ir::TrapCode::UnreachableCodeReached);
|
||||||
|
state.reachable = false;
|
||||||
|
}
|
||||||
|
Err(err) => {
|
||||||
|
return Err(err);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
Operator::MemoryAtomicNotify { memarg } => {
|
Operator::MemoryAtomicNotify { memarg } => {
|
||||||
let heap_index = MemoryIndex::from_u32(memarg.memory);
|
let heap_index = MemoryIndex::from_u32(memarg.memory);
|
||||||
let heap = state.get_heap(builder.func, memarg.memory, environ)?;
|
let heap = state.get_heap(builder.func, memarg.memory, environ)?;
|
||||||
let count = state.pop1(); // 32 (fixed)
|
let count = state.pop1(); // 32 (fixed)
|
||||||
let addr = state.pop1(); // 32 (fixed)
|
let addr = state.pop1(); // 32 (fixed)
|
||||||
let addr = fold_atomic_mem_addr(addr, memarg, I32, builder);
|
let addr = fold_atomic_mem_addr(addr, memarg, I32, builder);
|
||||||
let res =
|
match environ.translate_atomic_notify(builder.cursor(), heap_index, heap, addr, count) {
|
||||||
environ.translate_atomic_notify(builder.cursor(), heap_index, heap, addr, count)?;
|
Ok(res) => {
|
||||||
state.push1(res);
|
state.push1(res);
|
||||||
}
|
}
|
||||||
|
Err(wasmer_types::WasmError::Unsupported(_err)) => {
|
||||||
|
// Simple return a zero as this function is needed for the __wasi_init_memory function
|
||||||
|
// but the equivalent notify.wait will not be called (as only one thread calls __start)
|
||||||
|
// hence these atomic operations are not needed
|
||||||
|
state.push1(builder.ins().iconst(I32, i64::from(0)));
|
||||||
|
}
|
||||||
|
Err(err) => {
|
||||||
|
return Err(err);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
Operator::I32AtomicLoad { memarg } => {
|
Operator::I32AtomicLoad { memarg } => {
|
||||||
translate_atomic_load(I32, I32, memarg, builder, state, environ)?
|
translate_atomic_load(I32, I32, memarg, builder, state, environ)?
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
// This file contains code from external sources.
|
// This file contains code from external sources.
|
||||||
// Attributions: https://github.com/wasmerio/wasmer/blob/master/ATTRIBUTIONS.md
|
// Attributions: https://github.com/wasmerio/wasmer/blob/master/ATTRIBUTIONS.md
|
||||||
use super::state::ModuleTranslationState;
|
use super::state::ModuleTranslationState;
|
||||||
use crate::lib::std::borrow::ToOwned;
|
|
||||||
use crate::lib::std::string::ToString;
|
use crate::lib::std::string::ToString;
|
||||||
use crate::lib::std::{boxed::Box, string::String, vec::Vec};
|
use crate::lib::std::{boxed::Box, string::String, vec::Vec};
|
||||||
use crate::translate_module;
|
use crate::translate_module;
|
||||||
@@ -9,13 +8,13 @@ use crate::wasmparser::{Operator, Range, Type};
|
|||||||
use std::convert::{TryFrom, TryInto};
|
use std::convert::{TryFrom, TryInto};
|
||||||
use wasmer_types::entity::PrimaryMap;
|
use wasmer_types::entity::PrimaryMap;
|
||||||
use wasmer_types::FunctionType;
|
use wasmer_types::FunctionType;
|
||||||
|
use wasmer_types::WasmResult;
|
||||||
use wasmer_types::{
|
use wasmer_types::{
|
||||||
CustomSectionIndex, DataIndex, DataInitializer, DataInitializerLocation, ElemIndex,
|
CustomSectionIndex, DataIndex, DataInitializer, DataInitializerLocation, ElemIndex,
|
||||||
ExportIndex, FunctionIndex, GlobalIndex, GlobalInit, GlobalType, ImportIndex,
|
ExportIndex, FunctionIndex, GlobalIndex, GlobalInit, GlobalType, ImportIndex,
|
||||||
LocalFunctionIndex, MemoryIndex, MemoryType, ModuleInfo, SignatureIndex, TableIndex,
|
LocalFunctionIndex, MemoryIndex, MemoryType, ModuleInfo, SignatureIndex, TableIndex,
|
||||||
TableInitializer, TableType,
|
TableInitializer, TableType,
|
||||||
};
|
};
|
||||||
use wasmer_types::{WasmError, WasmResult};
|
|
||||||
|
|
||||||
/// Contains function data: bytecode and its offset in the module.
|
/// Contains function data: bytecode and its offset in the module.
|
||||||
#[derive(Hash)]
|
#[derive(Hash)]
|
||||||
@@ -254,11 +253,6 @@ impl<'data> ModuleEnvironment<'data> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn declare_memory(&mut self, memory: MemoryType) -> WasmResult<()> {
|
pub(crate) fn declare_memory(&mut self, memory: MemoryType) -> WasmResult<()> {
|
||||||
if memory.shared {
|
|
||||||
return Err(WasmError::Unsupported(
|
|
||||||
"shared memories are not supported yet".to_owned(),
|
|
||||||
));
|
|
||||||
}
|
|
||||||
self.module.memories.push(memory);
|
self.module.memories.push(memory);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,10 +16,11 @@ use crate::trap::{catch_traps, Trap, TrapCode};
|
|||||||
use crate::vmcontext::{
|
use crate::vmcontext::{
|
||||||
memory_copy, memory_fill, VMBuiltinFunctionsArray, VMCallerCheckedAnyfunc, VMContext,
|
memory_copy, memory_fill, VMBuiltinFunctionsArray, VMCallerCheckedAnyfunc, VMContext,
|
||||||
VMFunctionContext, VMFunctionImport, VMFunctionKind, VMGlobalDefinition, VMGlobalImport,
|
VMFunctionContext, VMFunctionImport, VMFunctionKind, VMGlobalDefinition, VMGlobalImport,
|
||||||
VMMemoryImport, VMSharedSignatureIndex, VMTableDefinition, VMTableImport, VMTrampoline,
|
VMMemoryDefinition, VMMemoryImport, VMSharedSignatureIndex, VMTableDefinition, VMTableImport,
|
||||||
|
VMTrampoline,
|
||||||
};
|
};
|
||||||
|
use crate::LinearMemory;
|
||||||
use crate::{FunctionBodyPtr, MaybeInstanceOwned, TrapHandlerFn, VMFunctionBody};
|
use crate::{FunctionBodyPtr, MaybeInstanceOwned, TrapHandlerFn, VMFunctionBody};
|
||||||
use crate::{LinearMemory, VMMemoryDefinition};
|
|
||||||
use crate::{VMFuncRef, VMFunction, VMGlobal, VMMemory, VMTable};
|
use crate::{VMFuncRef, VMFunction, VMGlobal, VMMemory, VMTable};
|
||||||
pub use allocator::InstanceAllocator;
|
pub use allocator::InstanceAllocator;
|
||||||
use memoffset::offset_of;
|
use memoffset::offset_of;
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ pub use crate::function_env::VMFunctionEnvironment;
|
|||||||
pub use crate::global::*;
|
pub use crate::global::*;
|
||||||
pub use crate::imports::Imports;
|
pub use crate::imports::Imports;
|
||||||
pub use crate::instance::{InstanceAllocator, InstanceHandle};
|
pub use crate::instance::{InstanceAllocator, InstanceHandle};
|
||||||
pub use crate::memory::{initialize_memory_with_data, LinearMemory, VMMemory};
|
pub use crate::memory::{initialize_memory_with_data, LinearMemory, VMMemory, VMOwnedMemory, VMSharedMemory};
|
||||||
pub use crate::mmap::Mmap;
|
pub use crate::mmap::Mmap;
|
||||||
pub use crate::probestack::PROBESTACK;
|
pub use crate::probestack::PROBESTACK;
|
||||||
pub use crate::sig_registry::SignatureRegistry;
|
pub use crate::sig_registry::SignatureRegistry;
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ use std::cell::UnsafeCell;
|
|||||||
use std::convert::TryInto;
|
use std::convert::TryInto;
|
||||||
use std::ptr::NonNull;
|
use std::ptr::NonNull;
|
||||||
use std::slice;
|
use std::slice;
|
||||||
|
use std::sync::{Arc, RwLock};
|
||||||
use wasmer_types::{Bytes, MemoryError, MemoryStyle, MemoryType, Pages};
|
use wasmer_types::{Bytes, MemoryError, MemoryStyle, MemoryType, Pages};
|
||||||
|
|
||||||
// The memory mapped area
|
// The memory mapped area
|
||||||
@@ -156,6 +157,18 @@ pub struct VMOwnedMemory {
|
|||||||
unsafe impl Send for VMOwnedMemory {}
|
unsafe impl Send for VMOwnedMemory {}
|
||||||
unsafe impl Sync for VMOwnedMemory {}
|
unsafe impl Sync for VMOwnedMemory {}
|
||||||
|
|
||||||
|
/// A shared linear memory instance.
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct VMSharedMemory {
|
||||||
|
// The underlying allocation.
|
||||||
|
mmap: Arc<RwLock<WasmMmap>>,
|
||||||
|
// Configuration of this memory
|
||||||
|
config: VMMemoryConfig,
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe impl Send for VMSharedMemory {}
|
||||||
|
unsafe impl Sync for VMSharedMemory {}
|
||||||
|
|
||||||
impl VMOwnedMemory {
|
impl VMOwnedMemory {
|
||||||
/// Create a new linear memory instance with specified minimum and maximum number of wasm pages.
|
/// Create a new linear memory instance with specified minimum and maximum number of wasm pages.
|
||||||
///
|
///
|
||||||
@@ -259,6 +272,16 @@ impl VMOwnedMemory {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl VMOwnedMemory {
|
||||||
|
/// Converts this owned memory into shared memory
|
||||||
|
pub fn to_shared(self) -> VMSharedMemory {
|
||||||
|
VMSharedMemory {
|
||||||
|
mmap: Arc::new(RwLock::new(self.mmap)),
|
||||||
|
config: self.config,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl LinearMemory for VMOwnedMemory {
|
impl LinearMemory for VMOwnedMemory {
|
||||||
/// Returns the type for this memory.
|
/// Returns the type for this memory.
|
||||||
fn ty(&self) -> MemoryType {
|
fn ty(&self) -> MemoryType {
|
||||||
@@ -295,12 +318,85 @@ impl LinearMemory for VMOwnedMemory {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl VMSharedMemory {
|
||||||
|
/// Create a new linear memory instance with specified minimum and maximum number of wasm pages.
|
||||||
|
///
|
||||||
|
/// This creates a `Memory` with owned metadata: this can be used to create a memory
|
||||||
|
/// that will be imported into Wasm modules.
|
||||||
|
pub fn new(memory: &MemoryType, style: &MemoryStyle) -> Result<Self, MemoryError> {
|
||||||
|
Ok(VMOwnedMemory::new(memory, style)?.to_shared())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create a new linear memory instance with specified minimum and maximum number of wasm pages.
|
||||||
|
///
|
||||||
|
/// This creates a `Memory` with metadata owned by a VM, pointed to by
|
||||||
|
/// `vm_memory_location`: this can be used to create a local memory.
|
||||||
|
///
|
||||||
|
/// # Safety
|
||||||
|
/// - `vm_memory_location` must point to a valid location in VM memory.
|
||||||
|
pub unsafe fn from_definition(
|
||||||
|
memory: &MemoryType,
|
||||||
|
style: &MemoryStyle,
|
||||||
|
vm_memory_location: NonNull<VMMemoryDefinition>,
|
||||||
|
) -> Result<Self, MemoryError> {
|
||||||
|
Ok(VMOwnedMemory::from_definition(memory, style, vm_memory_location)?.to_shared())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl LinearMemory for VMSharedMemory {
|
||||||
|
/// Returns the type for this memory.
|
||||||
|
fn ty(&self) -> MemoryType {
|
||||||
|
let minimum = {
|
||||||
|
let guard = self.mmap.read().unwrap();
|
||||||
|
guard.size()
|
||||||
|
};
|
||||||
|
self.config.ty(minimum)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the size of hte memory in pages
|
||||||
|
fn size(&self) -> Pages {
|
||||||
|
let guard = self.mmap.read().unwrap();
|
||||||
|
guard.size()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the memory style for this memory.
|
||||||
|
fn style(&self) -> MemoryStyle {
|
||||||
|
self.config.style()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Grow memory by the specified amount of wasm pages.
|
||||||
|
///
|
||||||
|
/// Returns `None` if memory can't be grown by the specified amount
|
||||||
|
/// of wasm pages.
|
||||||
|
fn grow(&mut self, delta: Pages) -> Result<Pages, MemoryError> {
|
||||||
|
let mut guard = self.mmap.write().unwrap();
|
||||||
|
guard.grow(delta, self.config.clone())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return a `VMMemoryDefinition` for exposing the memory to compiled wasm code.
|
||||||
|
fn vmmemory(&self) -> NonNull<VMMemoryDefinition> {
|
||||||
|
let guard = self.mmap.read().unwrap();
|
||||||
|
guard.vm_memory_definition.as_ptr()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Owned memory can not be cloned (this will always return None)
|
||||||
|
fn try_clone(&self) -> Option<Box<dyn LinearMemory + 'static>> {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl From<VMOwnedMemory> for VMMemory {
|
impl From<VMOwnedMemory> for VMMemory {
|
||||||
fn from(mem: VMOwnedMemory) -> Self {
|
fn from(mem: VMOwnedMemory) -> Self {
|
||||||
Self(Box::new(mem))
|
Self(Box::new(mem))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<VMSharedMemory> for VMMemory {
|
||||||
|
fn from(mem: VMSharedMemory) -> Self {
|
||||||
|
Self(Box::new(mem))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Represents linear memory that can be either owned or shared
|
/// Represents linear memory that can be either owned or shared
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct VMMemory(pub Box<dyn LinearMemory + 'static>);
|
pub struct VMMemory(pub Box<dyn LinearMemory + 'static>);
|
||||||
@@ -357,8 +453,12 @@ impl VMMemory {
|
|||||||
///
|
///
|
||||||
/// This creates a `Memory` with owned metadata: this can be used to create a memory
|
/// This creates a `Memory` with owned metadata: this can be used to create a memory
|
||||||
/// that will be imported into Wasm modules.
|
/// that will be imported into Wasm modules.
|
||||||
pub fn new(memory: &MemoryType, style: &MemoryStyle) -> Result<Self, MemoryError> {
|
pub fn new(memory: &MemoryType, style: &MemoryStyle) -> Result<VMMemory, MemoryError> {
|
||||||
Ok(Self(Box::new(VMOwnedMemory::new(memory, style)?)))
|
Ok(if memory.shared {
|
||||||
|
Self(Box::new(VMSharedMemory::new(memory, style)?))
|
||||||
|
} else {
|
||||||
|
Self(Box::new(VMOwnedMemory::new(memory, style)?))
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the number of pages in the allocated memory block
|
/// Returns the number of pages in the allocated memory block
|
||||||
@@ -377,12 +477,20 @@ impl VMMemory {
|
|||||||
memory: &MemoryType,
|
memory: &MemoryType,
|
||||||
style: &MemoryStyle,
|
style: &MemoryStyle,
|
||||||
vm_memory_location: NonNull<VMMemoryDefinition>,
|
vm_memory_location: NonNull<VMMemoryDefinition>,
|
||||||
) -> Result<Self, MemoryError> {
|
) -> Result<VMMemory, MemoryError> {
|
||||||
Ok(Self(Box::new(VMOwnedMemory::from_definition(
|
Ok(if memory.shared {
|
||||||
|
Self(Box::new(VMSharedMemory::from_definition(
|
||||||
memory,
|
memory,
|
||||||
style,
|
style,
|
||||||
vm_memory_location,
|
vm_memory_location,
|
||||||
)?)))
|
)?))
|
||||||
|
} else {
|
||||||
|
Self(Box::new(VMOwnedMemory::from_definition(
|
||||||
|
memory,
|
||||||
|
style,
|
||||||
|
vm_memory_location,
|
||||||
|
)?))
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Creates VMMemory from a custom implementation - the following into implementations
|
/// Creates VMMemory from a custom implementation - the following into implementations
|
||||||
|
|||||||
@@ -78,6 +78,11 @@ impl StoreObjects {
|
|||||||
self.id
|
self.id
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Sets the ID of this store
|
||||||
|
pub fn set_id(&mut self, id: StoreId) {
|
||||||
|
self.id = id;
|
||||||
|
}
|
||||||
|
|
||||||
/// Returns a pair of mutable references from two handles.
|
/// Returns a pair of mutable references from two handles.
|
||||||
///
|
///
|
||||||
/// Panics if both handles point to the same object.
|
/// Panics if both handles point to the same object.
|
||||||
|
|||||||
Reference in New Issue
Block a user