Move the wasmtime crate directories form lib/* to wasmtime-*.
This follows a similar change to Cranelift made here: https://github.com/CraneStation/cranelift/pull/660
This commit is contained in:
104
wasmtime-runtime/src/export.rs
Normal file
104
wasmtime-runtime/src/export.rs
Normal file
@@ -0,0 +1,104 @@
|
||||
use crate::vmcontext::{
|
||||
VMContext, VMFunctionBody, VMGlobalDefinition, VMMemoryDefinition, VMTableDefinition,
|
||||
};
|
||||
use cranelift_codegen::ir;
|
||||
use cranelift_wasm::Global;
|
||||
use wasmtime_environ::{MemoryPlan, TablePlan};
|
||||
|
||||
/// The value of an export passed from one instance to another.
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum Export {
|
||||
/// A function export value.
|
||||
Function {
|
||||
/// The address of the native-code function.
|
||||
address: *const VMFunctionBody,
|
||||
/// Pointer to the containing `VMContext`.
|
||||
vmctx: *mut VMContext,
|
||||
/// The function signature declaration, used for compatibilty checking.
|
||||
signature: ir::Signature,
|
||||
},
|
||||
|
||||
/// A table export value.
|
||||
Table {
|
||||
/// The address of the table descriptor.
|
||||
definition: *mut VMTableDefinition,
|
||||
/// Pointer to the containing `VMContext`.
|
||||
vmctx: *mut VMContext,
|
||||
/// The table declaration, used for compatibilty checking.
|
||||
table: TablePlan,
|
||||
},
|
||||
|
||||
/// A memory export value.
|
||||
Memory {
|
||||
/// The address of the memory descriptor.
|
||||
definition: *mut VMMemoryDefinition,
|
||||
/// Pointer to the containing `VMContext`.
|
||||
vmctx: *mut VMContext,
|
||||
/// The memory declaration, used for compatibilty checking.
|
||||
memory: MemoryPlan,
|
||||
},
|
||||
|
||||
/// A global export value.
|
||||
Global {
|
||||
/// The address of the global storage.
|
||||
definition: *mut VMGlobalDefinition,
|
||||
/// Pointer to the containing `VMContext`.
|
||||
vmctx: *mut VMContext,
|
||||
/// The global declaration, used for compatibilty checking.
|
||||
global: Global,
|
||||
},
|
||||
}
|
||||
|
||||
impl Export {
|
||||
/// Construct a function export value.
|
||||
pub fn function(
|
||||
address: *const VMFunctionBody,
|
||||
vmctx: *mut VMContext,
|
||||
signature: ir::Signature,
|
||||
) -> Self {
|
||||
Export::Function {
|
||||
address,
|
||||
vmctx,
|
||||
signature,
|
||||
}
|
||||
}
|
||||
|
||||
/// Construct a table export value.
|
||||
pub fn table(
|
||||
definition: *mut VMTableDefinition,
|
||||
vmctx: *mut VMContext,
|
||||
table: TablePlan,
|
||||
) -> Self {
|
||||
Export::Table {
|
||||
definition,
|
||||
vmctx,
|
||||
table,
|
||||
}
|
||||
}
|
||||
|
||||
/// Construct a memory export value.
|
||||
pub fn memory(
|
||||
definition: *mut VMMemoryDefinition,
|
||||
vmctx: *mut VMContext,
|
||||
memory: MemoryPlan,
|
||||
) -> Self {
|
||||
Export::Memory {
|
||||
definition,
|
||||
vmctx,
|
||||
memory,
|
||||
}
|
||||
}
|
||||
|
||||
/// Construct a global export value.
|
||||
pub fn global(
|
||||
definition: *mut VMGlobalDefinition,
|
||||
vmctx: *mut VMContext,
|
||||
global: Global,
|
||||
) -> Self {
|
||||
Export::Global {
|
||||
definition,
|
||||
vmctx,
|
||||
global,
|
||||
}
|
||||
}
|
||||
}
|
||||
54
wasmtime-runtime/src/imports.rs
Normal file
54
wasmtime-runtime/src/imports.rs
Normal file
@@ -0,0 +1,54 @@
|
||||
use crate::instance::InstanceHandle;
|
||||
use crate::vmcontext::{VMFunctionImport, VMGlobalImport, VMMemoryImport, VMTableImport};
|
||||
use cranelift_entity::{BoxedSlice, PrimaryMap};
|
||||
use cranelift_wasm::{FuncIndex, GlobalIndex, MemoryIndex, TableIndex};
|
||||
use std::collections::HashSet;
|
||||
|
||||
/// Resolved import pointers.
|
||||
#[derive(Clone)]
|
||||
pub struct Imports {
|
||||
/// The set of instances that the imports depend on.
|
||||
pub dependencies: HashSet<InstanceHandle>,
|
||||
|
||||
/// Resolved addresses for imported functions.
|
||||
pub functions: BoxedSlice<FuncIndex, VMFunctionImport>,
|
||||
|
||||
/// Resolved addresses for imported tables.
|
||||
pub tables: BoxedSlice<TableIndex, VMTableImport>,
|
||||
|
||||
/// Resolved addresses for imported memories.
|
||||
pub memories: BoxedSlice<MemoryIndex, VMMemoryImport>,
|
||||
|
||||
/// Resolved addresses for imported globals.
|
||||
pub globals: BoxedSlice<GlobalIndex, VMGlobalImport>,
|
||||
}
|
||||
|
||||
impl Imports {
|
||||
/// Construct a new `Imports` instance.
|
||||
pub fn new(
|
||||
dependencies: HashSet<InstanceHandle>,
|
||||
function_imports: PrimaryMap<FuncIndex, VMFunctionImport>,
|
||||
table_imports: PrimaryMap<TableIndex, VMTableImport>,
|
||||
memory_imports: PrimaryMap<MemoryIndex, VMMemoryImport>,
|
||||
global_imports: PrimaryMap<GlobalIndex, VMGlobalImport>,
|
||||
) -> Self {
|
||||
Self {
|
||||
dependencies,
|
||||
functions: function_imports.into_boxed_slice(),
|
||||
tables: table_imports.into_boxed_slice(),
|
||||
memories: memory_imports.into_boxed_slice(),
|
||||
globals: global_imports.into_boxed_slice(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Construct a new `Imports` instance with no imports.
|
||||
pub fn none() -> Self {
|
||||
Self {
|
||||
dependencies: HashSet::new(),
|
||||
functions: PrimaryMap::new().into_boxed_slice(),
|
||||
tables: PrimaryMap::new().into_boxed_slice(),
|
||||
memories: PrimaryMap::new().into_boxed_slice(),
|
||||
globals: PrimaryMap::new().into_boxed_slice(),
|
||||
}
|
||||
}
|
||||
}
|
||||
1180
wasmtime-runtime/src/instance.rs
Normal file
1180
wasmtime-runtime/src/instance.rs
Normal file
File diff suppressed because it is too large
Load Diff
113
wasmtime-runtime/src/jit_int.rs
Normal file
113
wasmtime-runtime/src/jit_int.rs
Normal file
@@ -0,0 +1,113 @@
|
||||
//! The GDB's JIT compilation interface. The low level module that exposes
|
||||
//! the __jit_debug_register_code() and __jit_debug_descriptor to register
|
||||
//! or unregister generated object images with debuggers.
|
||||
|
||||
use std::boxed::Box;
|
||||
use std::ptr;
|
||||
|
||||
#[repr(C)]
|
||||
struct JITCodeEntry {
|
||||
next_entry: *mut JITCodeEntry,
|
||||
prev_entry: *mut JITCodeEntry,
|
||||
symfile_addr: *const u8,
|
||||
symfile_size: u64,
|
||||
}
|
||||
|
||||
const JIT_NOACTION: u32 = 0;
|
||||
const JIT_REGISTER_FN: u32 = 1;
|
||||
const JIT_UNREGISTER_FN: u32 = 2;
|
||||
|
||||
#[repr(C)]
|
||||
struct JITDescriptor {
|
||||
version: u32,
|
||||
action_flag: u32,
|
||||
relevant_entry: *mut JITCodeEntry,
|
||||
first_entry: *mut JITCodeEntry,
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
#[used]
|
||||
static mut __jit_debug_descriptor: JITDescriptor = JITDescriptor {
|
||||
version: 1,
|
||||
action_flag: JIT_NOACTION,
|
||||
relevant_entry: ptr::null_mut(),
|
||||
first_entry: ptr::null_mut(),
|
||||
};
|
||||
|
||||
#[no_mangle]
|
||||
#[inline(never)]
|
||||
extern "C" fn __jit_debug_register_code() {}
|
||||
|
||||
/// Registeration for JIT image
|
||||
pub struct GdbJitImageRegistration {
|
||||
entry: *mut JITCodeEntry,
|
||||
file: Vec<u8>,
|
||||
}
|
||||
|
||||
impl GdbJitImageRegistration {
|
||||
/// Registers JIT image using __jit_debug_register_code
|
||||
pub fn register(file: Vec<u8>) -> GdbJitImageRegistration {
|
||||
GdbJitImageRegistration {
|
||||
entry: unsafe { register_gdb_jit_image(&file) },
|
||||
file,
|
||||
}
|
||||
}
|
||||
|
||||
/// JIT image used in registration
|
||||
pub fn file(&self) -> &[u8] {
|
||||
&self.file
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for GdbJitImageRegistration {
|
||||
fn drop(&mut self) {
|
||||
unsafe {
|
||||
unregister_gdb_jit_image(self.entry);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
unsafe fn register_gdb_jit_image(file: &[u8]) -> *mut JITCodeEntry {
|
||||
// Create a code entry for the file, which gives the start and size of the symbol file.
|
||||
let entry = Box::into_raw(Box::new(JITCodeEntry {
|
||||
next_entry: __jit_debug_descriptor.first_entry,
|
||||
prev_entry: ptr::null_mut(),
|
||||
symfile_addr: file.as_ptr(),
|
||||
symfile_size: file.len() as u64,
|
||||
}));
|
||||
// Add it to the linked list in the JIT descriptor.
|
||||
if __jit_debug_descriptor.first_entry != ptr::null_mut() {
|
||||
(*__jit_debug_descriptor.first_entry).prev_entry = entry;
|
||||
}
|
||||
__jit_debug_descriptor.first_entry = entry;
|
||||
// Point the relevant_entry field of the descriptor at the entry.
|
||||
__jit_debug_descriptor.relevant_entry = entry;
|
||||
// Set action_flag to JIT_REGISTER and call __jit_debug_register_code.
|
||||
__jit_debug_descriptor.action_flag = JIT_REGISTER_FN;
|
||||
__jit_debug_register_code();
|
||||
|
||||
__jit_debug_descriptor.action_flag = JIT_NOACTION;
|
||||
__jit_debug_descriptor.relevant_entry = ptr::null_mut();
|
||||
entry
|
||||
}
|
||||
|
||||
unsafe fn unregister_gdb_jit_image(entry: *mut JITCodeEntry) {
|
||||
// Remove the code entry corresponding to the code from the linked list.
|
||||
if (*entry).prev_entry != ptr::null_mut() {
|
||||
(*(*entry).prev_entry).next_entry = (*entry).next_entry;
|
||||
} else {
|
||||
__jit_debug_descriptor.first_entry = (*entry).next_entry;
|
||||
}
|
||||
if (*entry).next_entry != ptr::null_mut() {
|
||||
(*(*entry).next_entry).prev_entry = (*entry).prev_entry;
|
||||
}
|
||||
// Point the relevant_entry field of the descriptor at the code entry.
|
||||
__jit_debug_descriptor.relevant_entry = entry;
|
||||
// Set action_flag to JIT_UNREGISTER and call __jit_debug_register_code.
|
||||
__jit_debug_descriptor.action_flag = JIT_UNREGISTER_FN;
|
||||
__jit_debug_register_code();
|
||||
|
||||
__jit_debug_descriptor.action_flag = JIT_NOACTION;
|
||||
__jit_debug_descriptor.relevant_entry = ptr::null_mut();
|
||||
let _box = Box::from_raw(entry);
|
||||
}
|
||||
62
wasmtime-runtime/src/lib.rs
Normal file
62
wasmtime-runtime/src/lib.rs
Normal file
@@ -0,0 +1,62 @@
|
||||
//! Runtime library support for Wasmtime.
|
||||
|
||||
#![deny(missing_docs, trivial_numeric_casts, unused_extern_crates)]
|
||||
#![warn(unused_import_braces)]
|
||||
#![cfg_attr(feature = "std", deny(unstable_features))]
|
||||
#![cfg_attr(feature = "clippy", plugin(clippy(conf_file = "../../clippy.toml")))]
|
||||
#![cfg_attr(
|
||||
feature = "cargo-clippy",
|
||||
allow(clippy::new_without_default, clippy::new_without_default_derive)
|
||||
)]
|
||||
#![cfg_attr(
|
||||
feature = "cargo-clippy",
|
||||
warn(
|
||||
clippy::float_arithmetic,
|
||||
clippy::mut_mut,
|
||||
clippy::nonminimal_bool,
|
||||
clippy::option_map_unwrap_or,
|
||||
clippy::option_map_unwrap_or_else,
|
||||
clippy::print_stdout,
|
||||
clippy::unicode_not_nfc,
|
||||
clippy::use_self
|
||||
)
|
||||
)]
|
||||
|
||||
#[macro_use]
|
||||
extern crate lazy_static;
|
||||
#[macro_use]
|
||||
extern crate memoffset;
|
||||
#[macro_use]
|
||||
extern crate failure_derive;
|
||||
#[cfg(target_os = "windows")]
|
||||
extern crate winapi;
|
||||
|
||||
mod export;
|
||||
mod imports;
|
||||
mod instance;
|
||||
mod jit_int;
|
||||
mod memory;
|
||||
mod mmap;
|
||||
mod sig_registry;
|
||||
mod signalhandlers;
|
||||
mod table;
|
||||
mod traphandlers;
|
||||
mod vmcontext;
|
||||
|
||||
pub mod libcalls;
|
||||
|
||||
pub use crate::export::Export;
|
||||
pub use crate::imports::Imports;
|
||||
pub use crate::instance::{InstanceHandle, InstantiationError, LinkError};
|
||||
pub use crate::jit_int::GdbJitImageRegistration;
|
||||
pub use crate::mmap::Mmap;
|
||||
pub use crate::sig_registry::SignatureRegistry;
|
||||
pub use crate::signalhandlers::{wasmtime_init_eager, wasmtime_init_finish};
|
||||
pub use crate::traphandlers::{wasmtime_call, wasmtime_call_trampoline};
|
||||
pub use crate::vmcontext::{
|
||||
VMContext, VMFunctionBody, VMFunctionImport, VMGlobalDefinition, VMGlobalImport,
|
||||
VMMemoryDefinition, VMMemoryImport, VMSharedSignatureIndex, VMTableDefinition, VMTableImport,
|
||||
};
|
||||
|
||||
/// Version number of this crate.
|
||||
pub const VERSION: &str = env!("CARGO_PKG_VERSION");
|
||||
139
wasmtime-runtime/src/libcalls.rs
Normal file
139
wasmtime-runtime/src/libcalls.rs
Normal file
@@ -0,0 +1,139 @@
|
||||
//! Runtime library calls. Note that wasm compilers may sometimes perform these
|
||||
//! inline rather than calling them, particularly when CPUs have special
|
||||
//! instructions which compute them directly.
|
||||
|
||||
use crate::vmcontext::VMContext;
|
||||
use cranelift_wasm::{DefinedMemoryIndex, MemoryIndex};
|
||||
|
||||
/// Implementation of f32.ceil
|
||||
pub extern "C" fn wasmtime_f32_ceil(x: f32) -> f32 {
|
||||
x.ceil()
|
||||
}
|
||||
|
||||
/// Implementation of f32.floor
|
||||
pub extern "C" fn wasmtime_f32_floor(x: f32) -> f32 {
|
||||
x.floor()
|
||||
}
|
||||
|
||||
/// Implementation of f32.trunc
|
||||
pub extern "C" fn wasmtime_f32_trunc(x: f32) -> f32 {
|
||||
x.trunc()
|
||||
}
|
||||
|
||||
/// Implementation of f32.nearest
|
||||
#[allow(clippy::float_arithmetic, clippy::float_cmp)]
|
||||
pub extern "C" fn wasmtime_f32_nearest(x: f32) -> f32 {
|
||||
// Rust doesn't have a nearest function, so do it manually.
|
||||
if x == 0.0 {
|
||||
// Preserve the sign of zero.
|
||||
x
|
||||
} else {
|
||||
// Nearest is either ceil or floor depending on which is nearest or even.
|
||||
let u = x.ceil();
|
||||
let d = x.floor();
|
||||
let um = (x - u).abs();
|
||||
let dm = (x - d).abs();
|
||||
if um < dm
|
||||
|| (um == dm && {
|
||||
let h = u / 2.;
|
||||
h.floor() == h
|
||||
})
|
||||
{
|
||||
u
|
||||
} else {
|
||||
d
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Implementation of f64.ceil
|
||||
pub extern "C" fn wasmtime_f64_ceil(x: f64) -> f64 {
|
||||
x.ceil()
|
||||
}
|
||||
|
||||
/// Implementation of f64.floor
|
||||
pub extern "C" fn wasmtime_f64_floor(x: f64) -> f64 {
|
||||
x.floor()
|
||||
}
|
||||
|
||||
/// Implementation of f64.trunc
|
||||
pub extern "C" fn wasmtime_f64_trunc(x: f64) -> f64 {
|
||||
x.trunc()
|
||||
}
|
||||
|
||||
/// Implementation of f64.nearest
|
||||
#[allow(clippy::float_arithmetic, clippy::float_cmp)]
|
||||
pub extern "C" fn wasmtime_f64_nearest(x: f64) -> f64 {
|
||||
// Rust doesn't have a nearest function, so do it manually.
|
||||
if x == 0.0 {
|
||||
// Preserve the sign of zero.
|
||||
x
|
||||
} else {
|
||||
// Nearest is either ceil or floor depending on which is nearest or even.
|
||||
let u = x.ceil();
|
||||
let d = x.floor();
|
||||
let um = (x - u).abs();
|
||||
let dm = (x - d).abs();
|
||||
if um < dm
|
||||
|| (um == dm && {
|
||||
let h = u / 2.;
|
||||
h.floor() == h
|
||||
})
|
||||
{
|
||||
u
|
||||
} else {
|
||||
d
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Implementation of memory.grow for locally-defined 32-bit memories.
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn wasmtime_memory32_grow(
|
||||
vmctx: *mut VMContext,
|
||||
delta: u32,
|
||||
memory_index: u32,
|
||||
) -> u32 {
|
||||
let instance = (&mut *vmctx).instance();
|
||||
let memory_index = DefinedMemoryIndex::from_u32(memory_index);
|
||||
|
||||
instance
|
||||
.memory_grow(memory_index, delta)
|
||||
.unwrap_or(u32::max_value())
|
||||
}
|
||||
|
||||
/// Implementation of memory.grow for imported 32-bit memories.
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn wasmtime_imported_memory32_grow(
|
||||
vmctx: *mut VMContext,
|
||||
delta: u32,
|
||||
memory_index: u32,
|
||||
) -> u32 {
|
||||
let instance = (&mut *vmctx).instance();
|
||||
let memory_index = MemoryIndex::from_u32(memory_index);
|
||||
|
||||
instance
|
||||
.imported_memory_grow(memory_index, delta)
|
||||
.unwrap_or(u32::max_value())
|
||||
}
|
||||
|
||||
/// Implementation of memory.size for locally-defined 32-bit memories.
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn wasmtime_memory32_size(vmctx: *mut VMContext, memory_index: u32) -> u32 {
|
||||
let instance = (&mut *vmctx).instance();
|
||||
let memory_index = DefinedMemoryIndex::from_u32(memory_index);
|
||||
|
||||
instance.memory_size(memory_index)
|
||||
}
|
||||
|
||||
/// Implementation of memory.size for imported 32-bit memories.
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn wasmtime_imported_memory32_size(
|
||||
vmctx: *mut VMContext,
|
||||
memory_index: u32,
|
||||
) -> u32 {
|
||||
let instance = (&mut *vmctx).instance();
|
||||
let memory_index = MemoryIndex::from_u32(memory_index);
|
||||
|
||||
instance.imported_memory_size(memory_index)
|
||||
}
|
||||
137
wasmtime-runtime/src/memory.rs
Normal file
137
wasmtime-runtime/src/memory.rs
Normal file
@@ -0,0 +1,137 @@
|
||||
//! Memory management for linear memories.
|
||||
//!
|
||||
//! `LinearMemory` is to WebAssembly linear memories what `Table` is to WebAssembly tables.
|
||||
|
||||
use crate::mmap::Mmap;
|
||||
use crate::vmcontext::VMMemoryDefinition;
|
||||
use std::string::String;
|
||||
use wasmtime_environ::{MemoryPlan, MemoryStyle, WASM_MAX_PAGES, WASM_PAGE_SIZE};
|
||||
|
||||
/// A linear memory instance.
|
||||
#[derive(Debug)]
|
||||
pub struct LinearMemory {
|
||||
// The underlying allocation.
|
||||
mmap: Mmap,
|
||||
|
||||
// The current logical size in wasm pages of this linear memory.
|
||||
current: u32,
|
||||
|
||||
// The optional maximum size in wasm pages of this linear memory.
|
||||
maximum: Option<u32>,
|
||||
|
||||
// Size in bytes of extra guard pages after the end to optimize loads and stores with
|
||||
// constant offsets.
|
||||
offset_guard_size: usize,
|
||||
|
||||
// Records whether we're using a bounds-checking strategy which requires
|
||||
// handlers to catch trapping accesses.
|
||||
pub(crate) needs_signal_handlers: bool,
|
||||
}
|
||||
|
||||
impl LinearMemory {
|
||||
/// Create a new linear memory instance with specified minimum and maximum number of wasm pages.
|
||||
pub fn new(plan: &MemoryPlan) -> Result<Self, String> {
|
||||
// `maximum` cannot be set to more than `65536` pages.
|
||||
assert!(plan.memory.minimum <= WASM_MAX_PAGES);
|
||||
assert!(plan.memory.maximum.is_none() || plan.memory.maximum.unwrap() <= WASM_MAX_PAGES);
|
||||
|
||||
let offset_guard_bytes = plan.offset_guard_size as usize;
|
||||
|
||||
// If we have an offset guard, or if we're doing the static memory
|
||||
// allocation strategy, we need signal handlers to catch out of bounds
|
||||
// acceses.
|
||||
let needs_signal_handlers = offset_guard_bytes > 0
|
||||
|| match plan.style {
|
||||
MemoryStyle::Dynamic => false,
|
||||
MemoryStyle::Static { .. } => true,
|
||||
};
|
||||
|
||||
let minimum_pages = match plan.style {
|
||||
MemoryStyle::Dynamic => plan.memory.minimum,
|
||||
MemoryStyle::Static { bound } => {
|
||||
assert!(bound >= plan.memory.minimum);
|
||||
bound
|
||||
}
|
||||
} as usize;
|
||||
let minimum_bytes = minimum_pages.checked_mul(WASM_PAGE_SIZE as usize).unwrap();
|
||||
let request_bytes = minimum_bytes.checked_add(offset_guard_bytes).unwrap();
|
||||
let mapped_pages = plan.memory.minimum as usize;
|
||||
let mapped_bytes = mapped_pages * WASM_PAGE_SIZE as usize;
|
||||
|
||||
let mmap = Mmap::accessible_reserved(mapped_bytes, request_bytes)?;
|
||||
|
||||
Ok(Self {
|
||||
mmap,
|
||||
current: plan.memory.minimum,
|
||||
maximum: plan.memory.maximum,
|
||||
offset_guard_size: offset_guard_bytes,
|
||||
needs_signal_handlers,
|
||||
})
|
||||
}
|
||||
|
||||
/// Returns the number of allocated wasm pages.
|
||||
pub fn size(&self) -> u32 {
|
||||
self.current
|
||||
}
|
||||
|
||||
/// Grow memory by the specified amount of wasm pages.
|
||||
///
|
||||
/// Returns `None` if memory can't be grown by the specified amount
|
||||
/// of wasm pages.
|
||||
pub fn grow(&mut self, delta: u32) -> Option<u32> {
|
||||
let new_pages = match self.current.checked_add(delta) {
|
||||
Some(new_pages) => new_pages,
|
||||
// Linear memory size overflow.
|
||||
None => return None,
|
||||
};
|
||||
let prev_pages = self.current;
|
||||
|
||||
if let Some(maximum) = self.maximum {
|
||||
if new_pages > maximum {
|
||||
// Linear memory size would exceed the declared maximum.
|
||||
return None;
|
||||
}
|
||||
}
|
||||
|
||||
// Wasm linear memories are never allowed to grow beyond what is
|
||||
// indexable. If the memory has no maximum, enforce the greatest
|
||||
// limit here.
|
||||
if new_pages >= WASM_MAX_PAGES {
|
||||
// Linear memory size would exceed the index range.
|
||||
return None;
|
||||
}
|
||||
|
||||
let delta_bytes = cast::usize(delta) * WASM_PAGE_SIZE as usize;
|
||||
let prev_bytes = cast::usize(prev_pages) * WASM_PAGE_SIZE as usize;
|
||||
let new_bytes = cast::usize(new_pages) * WASM_PAGE_SIZE as usize;
|
||||
|
||||
if new_bytes > self.mmap.len() - self.offset_guard_size {
|
||||
// If the new size is within the declared maximum, but needs more memory than we
|
||||
// have on hand, it's a dynamic heap and it can move.
|
||||
let guard_bytes = self.offset_guard_size;
|
||||
let request_bytes = new_bytes.checked_add(guard_bytes)?;
|
||||
|
||||
let mut new_mmap = Mmap::accessible_reserved(new_bytes, request_bytes).ok()?;
|
||||
|
||||
let copy_len = self.mmap.len() - self.offset_guard_size;
|
||||
new_mmap.as_mut_slice()[..copy_len].copy_from_slice(&self.mmap.as_slice()[..copy_len]);
|
||||
|
||||
self.mmap = new_mmap;
|
||||
} else if delta_bytes > 0 {
|
||||
// Make the newly allocated pages accessible.
|
||||
self.mmap.make_accessible(prev_bytes, delta_bytes).ok()?;
|
||||
}
|
||||
|
||||
self.current = new_pages;
|
||||
|
||||
Some(prev_pages)
|
||||
}
|
||||
|
||||
/// Return a `VMMemoryDefinition` for exposing the memory to compiled wasm code.
|
||||
pub fn vmmemory(&mut self) -> VMMemoryDefinition {
|
||||
VMMemoryDefinition {
|
||||
base: self.mmap.as_mut_ptr(),
|
||||
current_length: self.current as usize * WASM_PAGE_SIZE as usize,
|
||||
}
|
||||
}
|
||||
}
|
||||
264
wasmtime-runtime/src/mmap.rs
Normal file
264
wasmtime-runtime/src/mmap.rs
Normal file
@@ -0,0 +1,264 @@
|
||||
//! Low-level abstraction for allocating and managing zero-filled pages
|
||||
//! of memory.
|
||||
|
||||
use core::ptr;
|
||||
use core::slice;
|
||||
use errno;
|
||||
use libc;
|
||||
use region;
|
||||
use std::string::{String, ToString};
|
||||
use std::vec::Vec;
|
||||
|
||||
/// Round `size` up to the nearest multiple of `page_size`.
|
||||
fn round_up_to_page_size(size: usize, page_size: usize) -> usize {
|
||||
(size + (page_size - 1)) & !(page_size - 1)
|
||||
}
|
||||
|
||||
/// A simple struct consisting of a page-aligned pointer to page-aligned
|
||||
/// and initially-zeroed memory and a length.
|
||||
#[derive(Debug)]
|
||||
pub struct Mmap {
|
||||
ptr: *mut u8,
|
||||
len: usize,
|
||||
}
|
||||
|
||||
impl Mmap {
|
||||
/// Construct a new empty instance of `Mmap`.
|
||||
pub fn new() -> Self {
|
||||
// Rust's slices require non-null pointers, even when empty. `Vec`
|
||||
// contains code to create a non-null dangling pointer value when
|
||||
// constructed empty, so we reuse that here.
|
||||
Self {
|
||||
ptr: Vec::new().as_mut_ptr(),
|
||||
len: 0,
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a new `Mmap` pointing to at least `size` bytes of page-aligned accessible memory.
|
||||
pub fn with_at_least(size: usize) -> Result<Self, String> {
|
||||
let page_size = region::page::size();
|
||||
let rounded_size = round_up_to_page_size(size, page_size);
|
||||
Self::accessible_reserved(rounded_size, rounded_size)
|
||||
}
|
||||
|
||||
/// Create a new `Mmap` pointing to `accessible_size` bytes of page-aligned accessible memory,
|
||||
/// within a reserved mapping of `mapping_size` bytes. `accessible_size` and `mapping_size`
|
||||
/// must be native page-size multiples.
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
pub fn accessible_reserved(
|
||||
accessible_size: usize,
|
||||
mapping_size: usize,
|
||||
) -> Result<Self, String> {
|
||||
let page_size = region::page::size();
|
||||
assert!(accessible_size <= mapping_size);
|
||||
assert_eq!(mapping_size & (page_size - 1), 0);
|
||||
assert_eq!(accessible_size & (page_size - 1), 0);
|
||||
|
||||
// Mmap may return EINVAL if the size is zero, so just
|
||||
// special-case that.
|
||||
if mapping_size == 0 {
|
||||
return Ok(Self::new());
|
||||
}
|
||||
|
||||
Ok(if accessible_size == mapping_size {
|
||||
// Allocate a single read-write region at once.
|
||||
let ptr = unsafe {
|
||||
libc::mmap(
|
||||
ptr::null_mut(),
|
||||
mapping_size,
|
||||
libc::PROT_READ | libc::PROT_WRITE,
|
||||
libc::MAP_PRIVATE | libc::MAP_ANON,
|
||||
-1,
|
||||
0,
|
||||
)
|
||||
};
|
||||
if ptr as isize == -1_isize {
|
||||
return Err(errno::errno().to_string());
|
||||
}
|
||||
|
||||
Self {
|
||||
ptr: ptr as *mut u8,
|
||||
len: mapping_size,
|
||||
}
|
||||
} else {
|
||||
// Reserve the mapping size.
|
||||
let ptr = unsafe {
|
||||
libc::mmap(
|
||||
ptr::null_mut(),
|
||||
mapping_size,
|
||||
libc::PROT_NONE,
|
||||
libc::MAP_PRIVATE | libc::MAP_ANON,
|
||||
-1,
|
||||
0,
|
||||
)
|
||||
};
|
||||
if ptr as isize == -1_isize {
|
||||
return Err(errno::errno().to_string());
|
||||
}
|
||||
|
||||
let mut result = Self {
|
||||
ptr: ptr as *mut u8,
|
||||
len: mapping_size,
|
||||
};
|
||||
|
||||
if accessible_size != 0 {
|
||||
// Commit the accessible size.
|
||||
result.make_accessible(0, accessible_size)?;
|
||||
}
|
||||
|
||||
result
|
||||
})
|
||||
}
|
||||
|
||||
/// Create a new `Mmap` pointing to `accessible_size` bytes of page-aligned accessible memory,
|
||||
/// within a reserved mapping of `mapping_size` bytes. `accessible_size` and `mapping_size`
|
||||
/// must be native page-size multiples.
|
||||
#[cfg(target_os = "windows")]
|
||||
pub fn accessible_reserved(
|
||||
accessible_size: usize,
|
||||
mapping_size: usize,
|
||||
) -> Result<Self, String> {
|
||||
use winapi::um::memoryapi::VirtualAlloc;
|
||||
use winapi::um::winnt::{MEM_COMMIT, MEM_RESERVE, PAGE_NOACCESS, PAGE_READWRITE};
|
||||
|
||||
let page_size = region::page::size();
|
||||
assert!(accessible_size <= mapping_size);
|
||||
assert_eq!(mapping_size & (page_size - 1), 0);
|
||||
assert_eq!(accessible_size & (page_size - 1), 0);
|
||||
|
||||
Ok(if accessible_size == mapping_size {
|
||||
// Allocate a single read-write region at once.
|
||||
let ptr = unsafe {
|
||||
VirtualAlloc(
|
||||
ptr::null_mut(),
|
||||
mapping_size,
|
||||
MEM_RESERVE | MEM_COMMIT,
|
||||
PAGE_READWRITE,
|
||||
)
|
||||
};
|
||||
if ptr.is_null() {
|
||||
return Err(errno::errno().to_string());
|
||||
}
|
||||
|
||||
Self {
|
||||
ptr: ptr as *mut u8,
|
||||
len: mapping_size,
|
||||
}
|
||||
} else {
|
||||
// Reserve the mapping size.
|
||||
let ptr =
|
||||
unsafe { VirtualAlloc(ptr::null_mut(), mapping_size, MEM_RESERVE, PAGE_NOACCESS) };
|
||||
if ptr.is_null() {
|
||||
return Err(errno::errno().to_string());
|
||||
}
|
||||
|
||||
let mut result = Self {
|
||||
ptr: ptr as *mut u8,
|
||||
len: mapping_size,
|
||||
};
|
||||
|
||||
if accessible_size != 0 {
|
||||
// Commit the accessible size.
|
||||
result.make_accessible(0, accessible_size)?;
|
||||
}
|
||||
|
||||
result
|
||||
})
|
||||
}
|
||||
|
||||
/// Make the memory starting at `start` and extending for `len` bytes accessible.
|
||||
/// `start` and `len` must be native page-size multiples and describe a range within
|
||||
/// `self`'s reserved memory.
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
pub fn make_accessible(&mut self, start: usize, len: usize) -> Result<(), String> {
|
||||
let page_size = region::page::size();
|
||||
assert_eq!(start & (page_size - 1), 0);
|
||||
assert_eq!(len & (page_size - 1), 0);
|
||||
assert!(len < self.len);
|
||||
assert!(start < self.len - len);
|
||||
|
||||
// Commit the accessible size.
|
||||
unsafe { region::protect(self.ptr.add(start), len, region::Protection::ReadWrite) }
|
||||
.map_err(|e| e.to_string())
|
||||
}
|
||||
|
||||
/// Make the memory starting at `start` and extending for `len` bytes accessible.
|
||||
/// `start` and `len` must be native page-size multiples and describe a range within
|
||||
/// `self`'s reserved memory.
|
||||
#[cfg(target_os = "windows")]
|
||||
pub fn make_accessible(&mut self, start: usize, len: usize) -> Result<(), String> {
|
||||
use winapi::um::memoryapi::VirtualAlloc;
|
||||
use winapi::um::winnt::{MEM_COMMIT, MEM_RESERVE, PAGE_NOACCESS, PAGE_READWRITE};
|
||||
|
||||
let page_size = region::page::size();
|
||||
assert_eq!(start & (page_size - 1), 0);
|
||||
assert_eq!(len & (page_size - 1), 0);
|
||||
assert!(len < self.len);
|
||||
assert!(start < self.len - len);
|
||||
|
||||
// Commit the accessible size.
|
||||
if unsafe { VirtualAlloc(self.ptr.add(start), len, MEM_COMMIT, PAGE_READWRITE) }.is_null() {
|
||||
return Err(errno::errno().to_string());
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Return the allocated memory as a slice of u8.
|
||||
pub fn as_slice(&self) -> &[u8] {
|
||||
unsafe { slice::from_raw_parts(self.ptr, self.len) }
|
||||
}
|
||||
|
||||
/// Return the allocated memory as a mutable slice of u8.
|
||||
pub fn as_mut_slice(&mut self) -> &mut [u8] {
|
||||
unsafe { slice::from_raw_parts_mut(self.ptr, self.len) }
|
||||
}
|
||||
|
||||
/// Return the allocated memory as a pointer to u8.
|
||||
pub fn as_ptr(&self) -> *const u8 {
|
||||
self.ptr
|
||||
}
|
||||
|
||||
/// Return the allocated memory as a mutable pointer to u8.
|
||||
pub fn as_mut_ptr(&mut self) -> *mut u8 {
|
||||
self.ptr
|
||||
}
|
||||
|
||||
/// Return the length of the allocated memory.
|
||||
pub fn len(&self) -> usize {
|
||||
self.len
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for Mmap {
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
fn drop(&mut self) {
|
||||
if self.len != 0 {
|
||||
let r = unsafe { libc::munmap(self.ptr as *mut libc::c_void, self.len) };
|
||||
assert_eq!(r, 0, "munmap failed: {}", errno::errno());
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
fn drop(&mut self) {
|
||||
if self.len != 0 {
|
||||
use winapi::um::memoryapi::VirtualFree;
|
||||
use winapi::um::winnt::MEM_RELEASE;
|
||||
let r = unsafe { VirtualFree(self.ptr as *mut libc::c_void, self.len, MEM_RELEASE) };
|
||||
assert_eq!(r, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_round_up_to_page_size() {
|
||||
assert_eq!(round_up_to_page_size(0, 4096), 0);
|
||||
assert_eq!(round_up_to_page_size(1, 4096), 4096);
|
||||
assert_eq!(round_up_to_page_size(4096, 4096), 4096);
|
||||
assert_eq!(round_up_to_page_size(4097, 4096), 8192);
|
||||
}
|
||||
}
|
||||
41
wasmtime-runtime/src/sig_registry.rs
Normal file
41
wasmtime-runtime/src/sig_registry.rs
Normal file
@@ -0,0 +1,41 @@
|
||||
//! Implement a registry of function signatures, for fast indirect call
|
||||
//! signature checking.
|
||||
|
||||
use crate::vmcontext::VMSharedSignatureIndex;
|
||||
use cast;
|
||||
use cranelift_codegen::ir;
|
||||
use std::collections::{hash_map, HashMap};
|
||||
|
||||
/// WebAssembly requires that the caller and callee signatures in an indirect
|
||||
/// call must match. To implement this efficiently, keep a registry of all
|
||||
/// signatures, shared by all instances, so that call sites can just do an
|
||||
/// index comparison.
|
||||
#[derive(Debug)]
|
||||
pub struct SignatureRegistry {
|
||||
signature_hash: HashMap<ir::Signature, VMSharedSignatureIndex>,
|
||||
}
|
||||
|
||||
impl SignatureRegistry {
|
||||
/// Create a new `SignatureRegistry`.
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
signature_hash: HashMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Register a signature and return its unique index.
|
||||
pub fn register(&mut self, sig: &ir::Signature) -> VMSharedSignatureIndex {
|
||||
let len = self.signature_hash.len();
|
||||
match self.signature_hash.entry(sig.clone()) {
|
||||
hash_map::Entry::Occupied(entry) => *entry.get(),
|
||||
hash_map::Entry::Vacant(entry) => {
|
||||
#[cfg(target_pointer_width = "32")]
|
||||
let sig_id = VMSharedSignatureIndex::new(cast::u32(len));
|
||||
#[cfg(target_pointer_width = "64")]
|
||||
let sig_id = VMSharedSignatureIndex::new(cast::u32(len).unwrap());
|
||||
entry.insert(sig_id);
|
||||
sig_id
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
121
wasmtime-runtime/src/signalhandlers.rs
Normal file
121
wasmtime-runtime/src/signalhandlers.rs
Normal file
@@ -0,0 +1,121 @@
|
||||
//! Interface to low-level signal-handling mechanisms.
|
||||
|
||||
#![allow(non_upper_case_globals)]
|
||||
#![allow(non_camel_case_types)]
|
||||
#![allow(non_snake_case)]
|
||||
|
||||
use crate::vmcontext::VMContext;
|
||||
use core::borrow::{Borrow, BorrowMut};
|
||||
use core::cell::RefCell;
|
||||
use std::sync::RwLock;
|
||||
|
||||
include!(concat!(env!("OUT_DIR"), "/signalhandlers.rs"));
|
||||
|
||||
struct InstallState {
|
||||
tried: bool,
|
||||
success: bool,
|
||||
}
|
||||
|
||||
impl InstallState {
|
||||
fn new() -> Self {
|
||||
Self {
|
||||
tried: false,
|
||||
success: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
lazy_static! {
|
||||
static ref EAGER_INSTALL_STATE: RwLock<InstallState> = RwLock::new(InstallState::new());
|
||||
static ref LAZY_INSTALL_STATE: RwLock<InstallState> = RwLock::new(InstallState::new());
|
||||
}
|
||||
|
||||
/// This function performs the low-overhead signal handler initialization that we
|
||||
/// want to do eagerly to ensure a more-deterministic global process state. This
|
||||
/// is especially relevant for signal handlers since handler ordering depends on
|
||||
/// installation order: the wasm signal handler must run *before* the other crash
|
||||
/// handlers and since POSIX signal handlers work LIFO, this function needs to be
|
||||
/// called at the end of the startup process, after other handlers have been
|
||||
/// installed. This function can thus be called multiple times, having no effect
|
||||
/// after the first call.
|
||||
#[no_mangle]
|
||||
pub extern "C" fn wasmtime_init_eager() {
|
||||
let mut locked = EAGER_INSTALL_STATE.write().unwrap();
|
||||
let state = locked.borrow_mut();
|
||||
|
||||
if state.tried {
|
||||
return;
|
||||
}
|
||||
|
||||
state.tried = true;
|
||||
assert!(!state.success);
|
||||
|
||||
if !unsafe { EnsureEagerSignalHandlers() } {
|
||||
return;
|
||||
}
|
||||
|
||||
state.success = true;
|
||||
}
|
||||
|
||||
thread_local! {
|
||||
static TRAP_CONTEXT: RefCell<TrapContext> =
|
||||
RefCell::new(TrapContext { triedToInstallSignalHandlers: false, haveSignalHandlers: false });
|
||||
}
|
||||
|
||||
/// Assuming `EnsureEagerProcessSignalHandlers` has already been called,
|
||||
/// this function performs the full installation of signal handlers which must
|
||||
/// be performed per-thread. This operation may incur some overhead and
|
||||
/// so should be done only when needed to use wasm.
|
||||
#[no_mangle]
|
||||
pub extern "C" fn wasmtime_init_finish(vmctx: &mut VMContext) {
|
||||
if !TRAP_CONTEXT.with(|cx| cx.borrow().triedToInstallSignalHandlers) {
|
||||
TRAP_CONTEXT.with(|cx| {
|
||||
cx.borrow_mut().triedToInstallSignalHandlers = true;
|
||||
assert!(!cx.borrow().haveSignalHandlers);
|
||||
});
|
||||
|
||||
{
|
||||
let locked = EAGER_INSTALL_STATE.read().unwrap();
|
||||
let state = locked.borrow();
|
||||
assert!(
|
||||
state.tried,
|
||||
"call wasmtime_init_eager before calling wasmtime_init_finish"
|
||||
);
|
||||
if !state.success {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(any(target_os = "macos", target_os = "ios"))]
|
||||
ensure_darwin_mach_ports();
|
||||
|
||||
TRAP_CONTEXT.with(|cx| {
|
||||
cx.borrow_mut().haveSignalHandlers = true;
|
||||
})
|
||||
}
|
||||
|
||||
let instance = unsafe { vmctx.instance() };
|
||||
let have_signal_handlers = TRAP_CONTEXT.with(|cx| cx.borrow().haveSignalHandlers);
|
||||
if !have_signal_handlers && instance.needs_signal_handlers() {
|
||||
panic!("failed to install signal handlers");
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(any(target_os = "macos", target_os = "ios"))]
|
||||
fn ensure_darwin_mach_ports() {
|
||||
let mut locked = LAZY_INSTALL_STATE.write().unwrap();
|
||||
let state = locked.borrow_mut();
|
||||
|
||||
if state.tried {
|
||||
return;
|
||||
}
|
||||
|
||||
state.tried = true;
|
||||
assert!(!state.success);
|
||||
|
||||
if !unsafe { EnsureDarwinMachPorts() } {
|
||||
return;
|
||||
}
|
||||
|
||||
state.success = true;
|
||||
}
|
||||
54
wasmtime-runtime/src/table.rs
Normal file
54
wasmtime-runtime/src/table.rs
Normal file
@@ -0,0 +1,54 @@
|
||||
//! Memory management for tables.
|
||||
//!
|
||||
//! `Table` is to WebAssembly tables what `LinearMemory` is to WebAssembly linear memories.
|
||||
|
||||
use crate::vmcontext::{VMCallerCheckedAnyfunc, VMTableDefinition};
|
||||
use cranelift_wasm::TableElementType;
|
||||
use std::vec::Vec;
|
||||
use wasmtime_environ::{TablePlan, TableStyle};
|
||||
|
||||
/// A table instance.
|
||||
#[derive(Debug)]
|
||||
pub struct Table {
|
||||
vec: Vec<VMCallerCheckedAnyfunc>,
|
||||
maximum: Option<u32>,
|
||||
}
|
||||
|
||||
impl Table {
|
||||
/// Create a new table instance with specified minimum and maximum number of elements.
|
||||
pub fn new(plan: &TablePlan) -> Self {
|
||||
match plan.table.ty {
|
||||
TableElementType::Func => (),
|
||||
TableElementType::Val(ty) => {
|
||||
unimplemented!("tables of types other than anyfunc ({})", ty)
|
||||
}
|
||||
};
|
||||
|
||||
match plan.style {
|
||||
TableStyle::CallerChecksSignature => Self {
|
||||
vec: vec![VMCallerCheckedAnyfunc::default(); plan.table.minimum as usize],
|
||||
maximum: plan.table.maximum,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/// Return a `VMTableDefinition` for exposing the table to compiled wasm code.
|
||||
pub fn vmtable(&mut self) -> VMTableDefinition {
|
||||
VMTableDefinition {
|
||||
base: self.vec.as_mut_ptr() as *mut u8,
|
||||
current_elements: self.vec.len(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl AsRef<[VMCallerCheckedAnyfunc]> for Table {
|
||||
fn as_ref(&self) -> &[VMCallerCheckedAnyfunc] {
|
||||
self.vec.as_slice()
|
||||
}
|
||||
}
|
||||
|
||||
impl AsMut<[VMCallerCheckedAnyfunc]> for Table {
|
||||
fn as_mut(&mut self) -> &mut [VMCallerCheckedAnyfunc] {
|
||||
self.vec.as_mut_slice()
|
||||
}
|
||||
}
|
||||
137
wasmtime-runtime/src/traphandlers.rs
Normal file
137
wasmtime-runtime/src/traphandlers.rs
Normal file
@@ -0,0 +1,137 @@
|
||||
//! WebAssembly trap handling, which is built on top of the lower-level
|
||||
//! signalhandling mechanisms.
|
||||
|
||||
use crate::signalhandlers::jmp_buf;
|
||||
use crate::vmcontext::{VMContext, VMFunctionBody};
|
||||
use core::cell::{Cell, RefCell};
|
||||
use core::mem;
|
||||
use core::ptr;
|
||||
use libc::c_int;
|
||||
use std::string::String;
|
||||
use std::vec::Vec;
|
||||
|
||||
// Currently we uset setjmp/longjmp to unwind out of a signal handler
|
||||
// and back to the point where WebAssembly was called (via `call_wasm`).
|
||||
// This works because WebAssembly code currently does not use any EH
|
||||
// or require any cleanups, and we never unwind through non-wasm frames.
|
||||
// In the future, we'll likely replace this with fancier stack unwinding.
|
||||
extern "C" {
|
||||
fn setjmp(env: *mut jmp_buf) -> c_int;
|
||||
fn longjmp(env: *const jmp_buf, val: c_int) -> !;
|
||||
}
|
||||
|
||||
thread_local! {
|
||||
static TRAP_PC: Cell<*const u8> = Cell::new(ptr::null());
|
||||
static JMP_BUFS: RefCell<Vec<jmp_buf>> = RefCell::new(Vec::new());
|
||||
}
|
||||
|
||||
/// Record the Trap code and wasm bytecode offset in TLS somewhere
|
||||
#[doc(hidden)]
|
||||
#[allow(non_snake_case)]
|
||||
#[no_mangle]
|
||||
pub extern "C" fn RecordTrap(pc: *const u8) {
|
||||
// TODO: Look up the wasm bytecode offset and trap code and record them instead.
|
||||
TRAP_PC.with(|data| data.set(pc));
|
||||
}
|
||||
|
||||
/// Initiate an unwind.
|
||||
#[doc(hidden)]
|
||||
#[allow(non_snake_case)]
|
||||
#[no_mangle]
|
||||
pub extern "C" fn Unwind() {
|
||||
JMP_BUFS.with(|bufs| {
|
||||
let buf = bufs.borrow_mut().pop().unwrap();
|
||||
unsafe { longjmp(&buf, 1) };
|
||||
})
|
||||
}
|
||||
|
||||
/// A simple guard to ensure that `JMP_BUFS` is reset when we're done.
|
||||
struct ScopeGuard {
|
||||
orig_num_bufs: usize,
|
||||
}
|
||||
|
||||
impl ScopeGuard {
|
||||
fn new() -> Self {
|
||||
assert_eq!(
|
||||
TRAP_PC.with(Cell::get),
|
||||
ptr::null(),
|
||||
"unfinished trap detected"
|
||||
);
|
||||
Self {
|
||||
orig_num_bufs: JMP_BUFS.with(|bufs| bufs.borrow().len()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for ScopeGuard {
|
||||
fn drop(&mut self) {
|
||||
let orig_num_bufs = self.orig_num_bufs;
|
||||
JMP_BUFS.with(|bufs| {
|
||||
bufs.borrow_mut()
|
||||
.resize(orig_num_bufs, unsafe { mem::zeroed() })
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
fn trap_message(_vmctx: *mut VMContext) -> String {
|
||||
let pc = TRAP_PC.with(|data| data.replace(ptr::null()));
|
||||
|
||||
// TODO: Record trap metadata in the VMContext, and look up the
|
||||
// pc to obtain the TrapCode and SourceLoc.
|
||||
|
||||
format!("wasm trap at {:?}", pc)
|
||||
}
|
||||
|
||||
fn push_jmp_buf(buf: jmp_buf) {
|
||||
JMP_BUFS.with(|bufs| bufs.borrow_mut().push(buf));
|
||||
}
|
||||
|
||||
/// Call the wasm function pointed to by `callee`. `values_vec` points to
|
||||
/// a buffer which holds the incoming arguments, and to which the outgoing
|
||||
/// return values will be written.
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn wasmtime_call_trampoline(
|
||||
vmctx: *mut VMContext,
|
||||
callee: *const VMFunctionBody,
|
||||
values_vec: *mut u8,
|
||||
) -> Result<(), String> {
|
||||
// Reset JMP_BUFS if the stack is unwound through this point.
|
||||
let _guard = ScopeGuard::new();
|
||||
|
||||
// Set a setjmp catch point.
|
||||
let mut buf = mem::uninitialized();
|
||||
if setjmp(&mut buf) != 0 {
|
||||
return Err(trap_message(vmctx));
|
||||
}
|
||||
push_jmp_buf(buf);
|
||||
|
||||
// Call the function!
|
||||
let func: fn(*mut VMContext, *mut u8) = mem::transmute(callee);
|
||||
func(vmctx, values_vec);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Call the wasm function pointed to by `callee`, which has no arguments or
|
||||
/// return values.
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn wasmtime_call(
|
||||
vmctx: *mut VMContext,
|
||||
callee: *const VMFunctionBody,
|
||||
) -> Result<(), String> {
|
||||
// Reset JMP_BUFS if the stack is unwound through this point.
|
||||
let _guard = ScopeGuard::new();
|
||||
|
||||
// Set a setjmp catch point.
|
||||
let mut buf = mem::uninitialized();
|
||||
if setjmp(&mut buf) != 0 {
|
||||
return Err(trap_message(vmctx));
|
||||
}
|
||||
push_jmp_buf(buf);
|
||||
|
||||
// Call the function!
|
||||
let func: fn(*mut VMContext) = mem::transmute(callee);
|
||||
func(vmctx);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
510
wasmtime-runtime/src/vmcontext.rs
Normal file
510
wasmtime-runtime/src/vmcontext.rs
Normal file
@@ -0,0 +1,510 @@
|
||||
//! This file declares `VMContext` and several related structs which contain
|
||||
//! fields that compiled wasm code accesses directly.
|
||||
|
||||
use crate::instance::Instance;
|
||||
use core::any::Any;
|
||||
use core::{ptr, u32};
|
||||
|
||||
/// An imported function.
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
#[repr(C)]
|
||||
pub struct VMFunctionImport {
|
||||
/// A pointer to the imported function body.
|
||||
pub body: *const VMFunctionBody,
|
||||
|
||||
/// A pointer to the `VMContext` that owns the function.
|
||||
pub vmctx: *mut VMContext,
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test_vmfunction_import {
|
||||
use super::VMFunctionImport;
|
||||
use core::mem::size_of;
|
||||
use wasmtime_environ::{Module, VMOffsets};
|
||||
|
||||
#[test]
|
||||
fn check_vmfunction_import_offsets() {
|
||||
let module = Module::new();
|
||||
let offsets = VMOffsets::new(size_of::<*mut u8>() as u8, &module);
|
||||
assert_eq!(
|
||||
size_of::<VMFunctionImport>(),
|
||||
usize::from(offsets.size_of_vmfunction_import())
|
||||
);
|
||||
assert_eq!(
|
||||
offset_of!(VMFunctionImport, body),
|
||||
usize::from(offsets.vmfunction_import_body())
|
||||
);
|
||||
assert_eq!(
|
||||
offset_of!(VMFunctionImport, vmctx),
|
||||
usize::from(offsets.vmfunction_import_vmctx())
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// A placeholder byte-sized type which is just used to provide some amount of type
|
||||
/// safety when dealing with pointers to JIT-compiled function bodies. Note that it's
|
||||
/// deliberately not Copy, as we shouldn't be carelessly copying function body bytes
|
||||
/// around.
|
||||
#[repr(C)]
|
||||
pub struct VMFunctionBody(u8);
|
||||
|
||||
#[cfg(test)]
|
||||
mod test_vmfunction_body {
|
||||
use super::VMFunctionBody;
|
||||
use core::mem::size_of;
|
||||
|
||||
#[test]
|
||||
fn check_vmfunction_body_offsets() {
|
||||
assert_eq!(size_of::<VMFunctionBody>(), 1);
|
||||
}
|
||||
}
|
||||
|
||||
/// The fields compiled code needs to access to utilize a WebAssembly table
|
||||
/// imported from another instance.
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
#[repr(C)]
|
||||
pub struct VMTableImport {
|
||||
/// A pointer to the imported table description.
|
||||
pub from: *mut VMTableDefinition,
|
||||
|
||||
/// A pointer to the `VMContext` that owns the table description.
|
||||
pub vmctx: *mut VMContext,
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test_vmtable_import {
|
||||
use super::VMTableImport;
|
||||
use core::mem::size_of;
|
||||
use wasmtime_environ::{Module, VMOffsets};
|
||||
|
||||
#[test]
|
||||
fn check_vmtable_import_offsets() {
|
||||
let module = Module::new();
|
||||
let offsets = VMOffsets::new(size_of::<*mut u8>() as u8, &module);
|
||||
assert_eq!(
|
||||
size_of::<VMTableImport>(),
|
||||
usize::from(offsets.size_of_vmtable_import())
|
||||
);
|
||||
assert_eq!(
|
||||
offset_of!(VMTableImport, from),
|
||||
usize::from(offsets.vmtable_import_from())
|
||||
);
|
||||
assert_eq!(
|
||||
offset_of!(VMTableImport, vmctx),
|
||||
usize::from(offsets.vmtable_import_vmctx())
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// The fields compiled code needs to access to utilize a WebAssembly linear
|
||||
/// memory imported from another instance.
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
#[repr(C)]
|
||||
pub struct VMMemoryImport {
|
||||
/// A pointer to the imported memory description.
|
||||
pub from: *mut VMMemoryDefinition,
|
||||
|
||||
/// A pointer to the `VMContext` that owns the memory description.
|
||||
pub vmctx: *mut VMContext,
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test_vmmemory_import {
|
||||
use super::VMMemoryImport;
|
||||
use core::mem::size_of;
|
||||
use wasmtime_environ::{Module, VMOffsets};
|
||||
|
||||
#[test]
|
||||
fn check_vmmemory_import_offsets() {
|
||||
let module = Module::new();
|
||||
let offsets = VMOffsets::new(size_of::<*mut u8>() as u8, &module);
|
||||
assert_eq!(
|
||||
size_of::<VMMemoryImport>(),
|
||||
usize::from(offsets.size_of_vmmemory_import())
|
||||
);
|
||||
assert_eq!(
|
||||
offset_of!(VMMemoryImport, from),
|
||||
usize::from(offsets.vmmemory_import_from())
|
||||
);
|
||||
assert_eq!(
|
||||
offset_of!(VMMemoryImport, vmctx),
|
||||
usize::from(offsets.vmmemory_import_vmctx())
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// The fields compiled code needs to access to utilize a WebAssembly global
|
||||
/// variable imported from another instance.
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
#[repr(C)]
|
||||
pub struct VMGlobalImport {
|
||||
/// A pointer to the imported global variable description.
|
||||
pub from: *mut VMGlobalDefinition,
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test_vmglobal_import {
|
||||
use super::VMGlobalImport;
|
||||
use core::mem::size_of;
|
||||
use wasmtime_environ::{Module, VMOffsets};
|
||||
|
||||
#[test]
|
||||
fn check_vmglobal_import_offsets() {
|
||||
let module = Module::new();
|
||||
let offsets = VMOffsets::new(size_of::<*mut u8>() as u8, &module);
|
||||
assert_eq!(
|
||||
size_of::<VMGlobalImport>(),
|
||||
usize::from(offsets.size_of_vmglobal_import())
|
||||
);
|
||||
assert_eq!(
|
||||
offset_of!(VMGlobalImport, from),
|
||||
usize::from(offsets.vmglobal_import_from())
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// The fields compiled code needs to access to utilize a WebAssembly linear
|
||||
/// memory defined within the instance, namely the start address and the
|
||||
/// size in bytes.
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
#[repr(C)]
|
||||
pub struct VMMemoryDefinition {
|
||||
/// The start address.
|
||||
pub base: *mut u8,
|
||||
|
||||
/// The current logical size of this linear memory in bytes.
|
||||
pub current_length: usize,
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test_vmmemory_definition {
|
||||
use super::VMMemoryDefinition;
|
||||
use core::mem::size_of;
|
||||
use wasmtime_environ::{Module, VMOffsets};
|
||||
|
||||
#[test]
|
||||
fn check_vmmemory_definition_offsets() {
|
||||
let module = Module::new();
|
||||
let offsets = VMOffsets::new(size_of::<*mut u8>() as u8, &module);
|
||||
assert_eq!(
|
||||
size_of::<VMMemoryDefinition>(),
|
||||
usize::from(offsets.size_of_vmmemory_definition())
|
||||
);
|
||||
assert_eq!(
|
||||
offset_of!(VMMemoryDefinition, base),
|
||||
usize::from(offsets.vmmemory_definition_base())
|
||||
);
|
||||
assert_eq!(
|
||||
offset_of!(VMMemoryDefinition, current_length),
|
||||
usize::from(offsets.vmmemory_definition_current_length())
|
||||
);
|
||||
/* TODO: Assert that the size of `current_length` matches.
|
||||
assert_eq!(
|
||||
size_of::<VMMemoryDefinition::current_length>(),
|
||||
usize::from(offsets.size_of_vmmemory_definition_current_length())
|
||||
);
|
||||
*/
|
||||
}
|
||||
}
|
||||
|
||||
/// The fields compiled code needs to access to utilize a WebAssembly table
|
||||
/// defined within the instance.
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
#[repr(C)]
|
||||
pub struct VMTableDefinition {
|
||||
/// Pointer to the table data.
|
||||
pub base: *mut u8,
|
||||
|
||||
/// The current number of elements in the table.
|
||||
pub current_elements: usize,
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test_vmtable_definition {
|
||||
use super::VMTableDefinition;
|
||||
use core::mem::size_of;
|
||||
use wasmtime_environ::{Module, VMOffsets};
|
||||
|
||||
#[test]
|
||||
fn check_vmtable_definition_offsets() {
|
||||
let module = Module::new();
|
||||
let offsets = VMOffsets::new(size_of::<*mut u8>() as u8, &module);
|
||||
assert_eq!(
|
||||
size_of::<VMTableDefinition>(),
|
||||
usize::from(offsets.size_of_vmtable_definition())
|
||||
);
|
||||
assert_eq!(
|
||||
offset_of!(VMTableDefinition, base),
|
||||
usize::from(offsets.vmtable_definition_base())
|
||||
);
|
||||
assert_eq!(
|
||||
offset_of!(VMTableDefinition, current_elements),
|
||||
usize::from(offsets.vmtable_definition_current_elements())
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// The storage for a WebAssembly global defined within the instance.
|
||||
///
|
||||
/// TODO: Pack the globals more densely, rather than using the same size
|
||||
/// for every type.
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
#[repr(C, align(8))]
|
||||
pub struct VMGlobalDefinition {
|
||||
storage: [u8; 8],
|
||||
// If more elements are added here, remember to add offset_of tests below!
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test_vmglobal_definition {
|
||||
use super::VMGlobalDefinition;
|
||||
use core::mem::{align_of, size_of};
|
||||
use wasmtime_environ::{Module, VMOffsets};
|
||||
|
||||
#[test]
|
||||
fn check_vmglobal_definition_alignment() {
|
||||
assert!(align_of::<VMGlobalDefinition>() >= align_of::<i32>());
|
||||
assert!(align_of::<VMGlobalDefinition>() >= align_of::<i64>());
|
||||
assert!(align_of::<VMGlobalDefinition>() >= align_of::<f32>());
|
||||
assert!(align_of::<VMGlobalDefinition>() >= align_of::<f64>());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn check_vmglobal_definition_offsets() {
|
||||
let module = Module::new();
|
||||
let offsets = VMOffsets::new(size_of::<*mut u8>() as u8, &module);
|
||||
assert_eq!(
|
||||
size_of::<VMGlobalDefinition>(),
|
||||
usize::from(offsets.size_of_vmglobal_definition())
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
impl VMGlobalDefinition {
|
||||
/// Construct a `VMGlobalDefinition`.
|
||||
pub fn new() -> Self {
|
||||
Self { storage: [0; 8] }
|
||||
}
|
||||
|
||||
/// Return a reference to the value as an i32.
|
||||
#[allow(clippy::cast_ptr_alignment)]
|
||||
pub unsafe fn as_i32(&self) -> &i32 {
|
||||
&*(self.storage.as_ref().as_ptr() as *const i32)
|
||||
}
|
||||
|
||||
/// Return a mutable reference to the value as an i32.
|
||||
#[allow(clippy::cast_ptr_alignment)]
|
||||
pub unsafe fn as_i32_mut(&mut self) -> &mut i32 {
|
||||
&mut *(self.storage.as_mut().as_mut_ptr() as *mut i32)
|
||||
}
|
||||
|
||||
/// Return a reference to the value as a u32.
|
||||
#[allow(clippy::cast_ptr_alignment)]
|
||||
pub unsafe fn as_u32(&self) -> &u32 {
|
||||
&*(self.storage.as_ref().as_ptr() as *const u32)
|
||||
}
|
||||
|
||||
/// Return a mutable reference to the value as an u32.
|
||||
#[allow(clippy::cast_ptr_alignment)]
|
||||
pub unsafe fn as_u32_mut(&mut self) -> &mut u32 {
|
||||
&mut *(self.storage.as_mut().as_mut_ptr() as *mut u32)
|
||||
}
|
||||
|
||||
/// Return a reference to the value as an i64.
|
||||
#[allow(clippy::cast_ptr_alignment)]
|
||||
pub unsafe fn as_i64(&self) -> &i64 {
|
||||
&*(self.storage.as_ref().as_ptr() as *const i64)
|
||||
}
|
||||
|
||||
/// Return a mutable reference to the value as an i64.
|
||||
#[allow(clippy::cast_ptr_alignment)]
|
||||
pub unsafe fn as_i64_mut(&mut self) -> &mut i64 {
|
||||
&mut *(self.storage.as_mut().as_mut_ptr() as *mut i64)
|
||||
}
|
||||
|
||||
/// Return a reference to the value as an u64.
|
||||
#[allow(clippy::cast_ptr_alignment)]
|
||||
pub unsafe fn as_u64(&self) -> &u64 {
|
||||
&*(self.storage.as_ref().as_ptr() as *const u64)
|
||||
}
|
||||
|
||||
/// Return a mutable reference to the value as an u64.
|
||||
#[allow(clippy::cast_ptr_alignment)]
|
||||
pub unsafe fn as_u64_mut(&mut self) -> &mut u64 {
|
||||
&mut *(self.storage.as_mut().as_mut_ptr() as *mut u64)
|
||||
}
|
||||
|
||||
/// Return a reference to the value as an f32.
|
||||
#[allow(clippy::cast_ptr_alignment)]
|
||||
pub unsafe fn as_f32(&self) -> &f32 {
|
||||
&*(self.storage.as_ref().as_ptr() as *const f32)
|
||||
}
|
||||
|
||||
/// Return a mutable reference to the value as an f32.
|
||||
#[allow(clippy::cast_ptr_alignment)]
|
||||
pub unsafe fn as_f32_mut(&mut self) -> &mut f32 {
|
||||
&mut *(self.storage.as_mut().as_mut_ptr() as *mut f32)
|
||||
}
|
||||
|
||||
/// Return a reference to the value as f32 bits.
|
||||
#[allow(clippy::cast_ptr_alignment)]
|
||||
pub unsafe fn as_f32_bits(&self) -> &u32 {
|
||||
&*(self.storage.as_ref().as_ptr() as *const u32)
|
||||
}
|
||||
|
||||
/// Return a mutable reference to the value as f32 bits.
|
||||
#[allow(clippy::cast_ptr_alignment)]
|
||||
pub unsafe fn as_f32_bits_mut(&mut self) -> &mut u32 {
|
||||
&mut *(self.storage.as_mut().as_mut_ptr() as *mut u32)
|
||||
}
|
||||
|
||||
/// Return a reference to the value as an f64.
|
||||
#[allow(clippy::cast_ptr_alignment)]
|
||||
pub unsafe fn as_f64(&self) -> &f64 {
|
||||
&*(self.storage.as_ref().as_ptr() as *const f64)
|
||||
}
|
||||
|
||||
/// Return a mutable reference to the value as an f64.
|
||||
#[allow(clippy::cast_ptr_alignment)]
|
||||
pub unsafe fn as_f64_mut(&mut self) -> &mut f64 {
|
||||
&mut *(self.storage.as_mut().as_mut_ptr() as *mut f64)
|
||||
}
|
||||
|
||||
/// Return a reference to the value as f64 bits.
|
||||
#[allow(clippy::cast_ptr_alignment)]
|
||||
pub unsafe fn as_f64_bits(&self) -> &u64 {
|
||||
&*(self.storage.as_ref().as_ptr() as *const u64)
|
||||
}
|
||||
|
||||
/// Return a mutable reference to the value as f64 bits.
|
||||
#[allow(clippy::cast_ptr_alignment)]
|
||||
pub unsafe fn as_f64_bits_mut(&mut self) -> &mut u64 {
|
||||
&mut *(self.storage.as_mut().as_mut_ptr() as *mut u64)
|
||||
}
|
||||
}
|
||||
|
||||
/// An index into the shared signature registry, usable for checking signatures
|
||||
/// at indirect calls.
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Eq, PartialEq, Clone, Copy)]
|
||||
pub struct VMSharedSignatureIndex(u32);
|
||||
|
||||
#[cfg(test)]
|
||||
mod test_vmshared_signature_index {
|
||||
use super::VMSharedSignatureIndex;
|
||||
use core::mem::size_of;
|
||||
use wasmtime_environ::{Module, TargetSharedSignatureIndex, VMOffsets};
|
||||
|
||||
#[test]
|
||||
fn check_vmshared_signature_index() {
|
||||
let module = Module::new();
|
||||
let offsets = VMOffsets::new(size_of::<*mut u8>() as u8, &module);
|
||||
assert_eq!(
|
||||
size_of::<VMSharedSignatureIndex>(),
|
||||
usize::from(offsets.size_of_vmshared_signature_index())
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn check_target_shared_signature_index() {
|
||||
assert_eq!(
|
||||
size_of::<VMSharedSignatureIndex>(),
|
||||
size_of::<TargetSharedSignatureIndex>()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
impl VMSharedSignatureIndex {
|
||||
/// Create a new `VMSharedSignatureIndex`.
|
||||
pub fn new(value: u32) -> Self {
|
||||
VMSharedSignatureIndex(value)
|
||||
}
|
||||
}
|
||||
|
||||
/// The VM caller-checked "anyfunc" record, for caller-side signature checking.
|
||||
/// It consists of the actual function pointer and a signature id to be checked
|
||||
/// by the caller.
|
||||
#[derive(Debug, Clone)]
|
||||
#[repr(C)]
|
||||
pub struct VMCallerCheckedAnyfunc {
|
||||
pub func_ptr: *const VMFunctionBody,
|
||||
pub type_index: VMSharedSignatureIndex,
|
||||
pub vmctx: *mut VMContext,
|
||||
// If more elements are added here, remember to add offset_of tests below!
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test_vmcaller_checked_anyfunc {
|
||||
use super::VMCallerCheckedAnyfunc;
|
||||
use core::mem::size_of;
|
||||
use wasmtime_environ::{Module, VMOffsets};
|
||||
|
||||
#[test]
|
||||
fn check_vmcaller_checked_anyfunc_offsets() {
|
||||
let module = Module::new();
|
||||
let offsets = VMOffsets::new(size_of::<*mut u8>() as u8, &module);
|
||||
assert_eq!(
|
||||
size_of::<VMCallerCheckedAnyfunc>(),
|
||||
usize::from(offsets.size_of_vmcaller_checked_anyfunc())
|
||||
);
|
||||
assert_eq!(
|
||||
offset_of!(VMCallerCheckedAnyfunc, func_ptr),
|
||||
usize::from(offsets.vmcaller_checked_anyfunc_func_ptr())
|
||||
);
|
||||
assert_eq!(
|
||||
offset_of!(VMCallerCheckedAnyfunc, type_index),
|
||||
usize::from(offsets.vmcaller_checked_anyfunc_type_index())
|
||||
);
|
||||
assert_eq!(
|
||||
offset_of!(VMCallerCheckedAnyfunc, vmctx),
|
||||
usize::from(offsets.vmcaller_checked_anyfunc_vmctx())
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for VMCallerCheckedAnyfunc {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
func_ptr: ptr::null_mut(),
|
||||
type_index: VMSharedSignatureIndex::new(u32::MAX),
|
||||
vmctx: ptr::null_mut(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The VM "context", which is pointed to by the `vmctx` arg in Cranelift.
|
||||
/// This has information about globals, memories, tables, and other runtime
|
||||
/// state associated with the current instance.
|
||||
///
|
||||
/// The struct here is empty, as the sizes of these fields are dynamic, and
|
||||
/// we can't describe them in Rust's type system. Sufficient memory is
|
||||
/// allocated at runtime.
|
||||
///
|
||||
/// TODO: We could move the globals into the `vmctx` allocation too.
|
||||
#[derive(Debug)]
|
||||
#[repr(C)]
|
||||
pub struct VMContext {}
|
||||
|
||||
impl VMContext {
|
||||
/// Return a mutable reference to the associated `Instance`.
|
||||
///
|
||||
/// This is unsafe because it doesn't work on just any `VMContext`, it must
|
||||
/// be a `VMContext` allocated as part of an `Instance`.
|
||||
#[allow(clippy::cast_ptr_alignment)]
|
||||
pub(crate) unsafe fn instance(&mut self) -> &mut Instance {
|
||||
&mut *((self as *mut Self as *mut u8).offset(-Instance::vmctx_offset()) as *mut Instance)
|
||||
}
|
||||
|
||||
/// Return a mutable reference to the host state associated with this `Instance`.
|
||||
///
|
||||
/// This is unsafe because it doesn't work on just any `VMContext`, it must
|
||||
/// be a `VMContext` allocated as part of an `Instance`.
|
||||
pub unsafe fn host_state(&mut self) -> &mut Any {
|
||||
self.instance().host_state()
|
||||
}
|
||||
|
||||
/// Lookup an export in the global exports namespace.
|
||||
pub unsafe fn lookup_global_export(&mut self, field: &str) -> Option<crate::export::Export> {
|
||||
self.instance().lookup_global_export(field)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user