use super::{Instance, VMInstance};
use crate::vmcontext::VMTableDefinition;
use crate::{VMGlobalDefinition, VMMemoryDefinition};
use std::alloc::{self, Layout};
use std::convert::TryFrom;
use std::mem;
use std::ptr::{self, NonNull};
use wasmer_types::entity::EntityRef;
use wasmer_types::{LocalGlobalIndex, VMOffsets};
use wasmer_types::{LocalMemoryIndex, LocalTableIndex, ModuleInfo};
pub struct InstanceAllocator {
instance_ptr: NonNull<Instance>,
instance_layout: Layout,
offsets: VMOffsets,
consumed: bool,
}
impl Drop for InstanceAllocator {
fn drop(&mut self) {
if !self.consumed {
let instance_ptr = self.instance_ptr.as_ptr();
unsafe {
std::alloc::dealloc(instance_ptr as *mut u8, self.instance_layout);
}
}
}
}
impl InstanceAllocator {
#[allow(clippy::type_complexity)]
pub fn new(
module: &ModuleInfo,
) -> (
Self,
Vec<NonNull<VMMemoryDefinition>>,
Vec<NonNull<VMTableDefinition>>,
Vec<NonNull<VMGlobalDefinition>>,
) {
let offsets = VMOffsets::new(mem::size_of::<usize>() as u8, module);
Self::new_with_offsets(offsets, module)
}
#[allow(clippy::type_complexity)]
pub fn new_with_offsets(
offsets: VMOffsets,
module: &ModuleInfo,
) -> (
Self,
Vec<NonNull<VMMemoryDefinition>>,
Vec<NonNull<VMTableDefinition>>,
Vec<NonNull<VMGlobalDefinition>>,
) {
let _ = module;
let instance_layout = Self::instance_layout(&offsets);
#[allow(clippy::cast_ptr_alignment)]
let instance_ptr = unsafe { alloc::alloc(instance_layout) as *mut Instance };
let instance_ptr = if let Some(ptr) = NonNull::new(instance_ptr) {
ptr
} else {
alloc::handle_alloc_error(instance_layout);
};
let allocator = Self {
instance_ptr,
instance_layout,
offsets,
consumed: false,
};
let memories = unsafe { allocator.memory_definition_locations() };
let tables = unsafe { allocator.table_definition_locations() };
let globals = unsafe { allocator.global_definition_locations() };
(allocator, memories, tables, globals)
}
fn instance_layout(offsets: &VMOffsets) -> Layout {
let vmctx_size = usize::try_from(offsets.size_of_vmctx())
.expect("Failed to convert the size of `vmctx` to a `usize`");
let instance_vmctx_layout =
Layout::array::<u8>(vmctx_size).expect("Failed to create a layout for `VMContext`");
let (instance_layout, _offset) = Layout::new::<Instance>()
.extend(instance_vmctx_layout)
.expect("Failed to extend to `Instance` layout to include `VMContext`");
instance_layout.pad_to_align()
}
unsafe fn memory_definition_locations(&self) -> Vec<NonNull<VMMemoryDefinition>> {
unsafe {
let num_memories = self.offsets.num_local_memories();
let num_memories = usize::try_from(num_memories).unwrap();
let mut out = Vec::with_capacity(num_memories);
let ptr = self.instance_ptr.cast::<u8>().as_ptr();
let base_ptr = ptr.add(mem::size_of::<Instance>());
for i in 0..num_memories {
let mem_offset = self
.offsets
.vmctx_vmmemory_definition(LocalMemoryIndex::new(i));
let mem_offset = usize::try_from(mem_offset).unwrap();
let new_ptr = NonNull::new_unchecked(base_ptr.add(mem_offset));
out.push(new_ptr.cast());
}
out
}
}
unsafe fn table_definition_locations(&self) -> Vec<NonNull<VMTableDefinition>> {
unsafe {
let num_tables = self.offsets.num_local_tables();
let num_tables = usize::try_from(num_tables).unwrap();
let mut out = Vec::with_capacity(num_tables);
let ptr = self.instance_ptr.cast::<u8>().as_ptr();
let base_ptr = ptr.add(std::mem::size_of::<Instance>());
for i in 0..num_tables {
let table_offset = self
.offsets
.vmctx_vmtable_definition(LocalTableIndex::new(i));
let table_offset = usize::try_from(table_offset).unwrap();
let new_ptr = NonNull::new_unchecked(base_ptr.add(table_offset));
out.push(new_ptr.cast());
}
out
}
}
unsafe fn global_definition_locations(&self) -> Vec<NonNull<VMGlobalDefinition>> {
unsafe {
let num_globals = self.offsets.num_local_globals();
let num_globals = usize::try_from(num_globals).unwrap();
let mut out = Vec::with_capacity(num_globals);
let ptr = self.instance_ptr.cast::<u8>().as_ptr();
let base_ptr = ptr.add(std::mem::size_of::<Instance>());
for i in 0..num_globals {
let global_offset = self
.offsets
.vmctx_vmglobal_definition(LocalGlobalIndex::new(i));
let global_offset = usize::try_from(global_offset).unwrap();
let new_ptr = NonNull::new_unchecked(base_ptr.add(global_offset));
out.push(new_ptr.cast());
}
out
}
}
pub(crate) fn into_vminstance(mut self, instance: Instance) -> VMInstance {
self.consumed = true;
unsafe {
ptr::write(self.instance_ptr.as_ptr(), instance);
}
let instance = self.instance_ptr;
let instance_layout = self.instance_layout;
VMInstance {
instance,
instance_layout,
}
}
pub(crate) fn offsets(&self) -> &VMOffsets {
&self.offsets
}
}
#[cfg(test)]
mod tests {
use super::*;
use wasmer_types::ModuleInfo;
#[test]
fn vmoffsets_new_is_deterministic_on_empty_module() {
let module = ModuleInfo::default();
let ps = mem::size_of::<usize>() as u8;
let a = VMOffsets::new(ps, &module);
let b = VMOffsets::new(ps, &module);
let a_dbg = format!("{a:?}");
let b_dbg = format!("{b:?}");
assert_eq!(a_dbg, b_dbg, "VMOffsets::new must be deterministic");
}
#[test]
fn new_with_offsets_matches_new() {
let module = ModuleInfo::default();
let ps = mem::size_of::<usize>() as u8;
let (a, _, _, _) = InstanceAllocator::new(&module);
let cached = VMOffsets::new(ps, &module);
let (b, _, _, _) = InstanceAllocator::new_with_offsets(cached, &module);
assert_eq!(
a.instance_layout.size(),
b.instance_layout.size(),
"instance_layout.size() mismatch"
);
assert_eq!(
a.instance_layout.align(),
b.instance_layout.align(),
"instance_layout.align() mismatch"
);
assert_eq!(
a.offsets.size_of_vmctx(),
b.offsets.size_of_vmctx(),
"size_of_vmctx mismatch, caching broke the layout invariant"
);
}
#[test]
fn new_with_offsets_many_times() {
let module = ModuleInfo::default();
let ps = mem::size_of::<usize>() as u8;
let expected_size = {
let (a, _, _, _) = InstanceAllocator::new(&module);
a.instance_layout.size()
};
for _ in 0..1024 {
let cached = VMOffsets::new(ps, &module);
let (b, _, _, _) = InstanceAllocator::new_with_offsets(cached, &module);
assert_eq!(b.instance_layout.size(), expected_size);
}
}
}