mirror of
https://github.com/fluencelabs/wasmer
synced 2025-06-25 22:51:32 +00:00
Add separated runtime crate
This commit is contained in:
11
lib/runtime/src/backend.rs
Normal file
11
lib/runtime/src/backend.rs
Normal file
@ -0,0 +1,11 @@
|
||||
use crate::{module::Module, types::FuncIndex, vm};
|
||||
use std::ptr::NonNull;
|
||||
|
||||
pub trait Compiler {
|
||||
/// Compiles a `Module` from WebAssembly binary format
|
||||
fn compile(&self, wasm: &[u8]) -> Result<Module, String>;
|
||||
}
|
||||
|
||||
pub trait FuncResolver {
|
||||
fn get(&self, module: &Module, index: FuncIndex) -> Option<NonNull<vm::Func>>;
|
||||
}
|
326
lib/runtime/src/backing.rs
Normal file
326
lib/runtime/src/backing.rs
Normal file
@ -0,0 +1,326 @@
|
||||
use crate::{
|
||||
instance::{Import, ImportResolver},
|
||||
memory::LinearMemory,
|
||||
module::{ImportName, Module},
|
||||
table::{TableBacking, TableElements},
|
||||
types::{Initializer, MapIndex, Value},
|
||||
vm,
|
||||
};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct LocalBacking {
|
||||
pub memories: Box<[LinearMemory]>,
|
||||
pub tables: Box<[TableBacking]>,
|
||||
|
||||
pub vm_memories: Box<[vm::LocalMemory]>,
|
||||
pub vm_tables: Box<[vm::LocalTable]>,
|
||||
pub vm_globals: Box<[vm::LocalGlobal]>,
|
||||
pub vm_signatures: Box<[vm::SigId]>,
|
||||
}
|
||||
|
||||
impl LocalBacking {
|
||||
pub fn new(module: &Module, imports: &ImportBacking) -> Self {
|
||||
let mut memories = Self::generate_memories(module);
|
||||
let mut tables = Self::generate_tables(module);
|
||||
let globals = Self::generate_globals(module);
|
||||
|
||||
let vm_memories = Self::finalize_memories(module, &mut memories[..]);
|
||||
let vm_tables = Self::finalize_tables(module, imports, &mut tables[..]);
|
||||
let vm_globals = Self::finalize_globals(module, imports, globals);
|
||||
let vm_signatures = module.sig_registry.into_vm_sigid();
|
||||
|
||||
Self {
|
||||
memories,
|
||||
tables,
|
||||
|
||||
vm_memories,
|
||||
vm_tables,
|
||||
vm_globals,
|
||||
vm_signatures,
|
||||
}
|
||||
}
|
||||
|
||||
fn generate_memories(module: &Module) -> Box<[LinearMemory]> {
|
||||
let mut memories = Vec::with_capacity(module.memories.len());
|
||||
|
||||
for (_, mem) in &module.memories {
|
||||
// If we use emscripten, we set a fixed initial and maximum
|
||||
debug!("Instance - init memory ({}, {:?})", mem.min, mem.max);
|
||||
// let memory = if options.abi == InstanceABI::Emscripten {
|
||||
// // We use MAX_PAGES, so at the end the result is:
|
||||
// // (initial * LinearMemory::PAGE_SIZE) == LinearMemory::DEFAULT_HEAP_SIZE
|
||||
// // However, it should be: (initial * LinearMemory::PAGE_SIZE) == 16777216
|
||||
// LinearMemory::new(LinearMemory::MAX_PAGES, None)
|
||||
// } else {
|
||||
// LinearMemory::new(memory.minimum, memory.maximum.map(|m| m as u32))
|
||||
// };
|
||||
let memory = LinearMemory::new(mem);
|
||||
memories.push(memory);
|
||||
}
|
||||
|
||||
memories.into_boxed_slice()
|
||||
}
|
||||
|
||||
fn finalize_memories(module: &Module, memories: &mut [LinearMemory]) -> Box<[vm::LocalMemory]> {
|
||||
for init in &module.data_initializers {
|
||||
assert!(init.base.is_none(), "global base not supported yet");
|
||||
assert!(
|
||||
init.offset + init.data.len() <= memories[init.memory_index.index()].current_size()
|
||||
);
|
||||
let offset = init.offset;
|
||||
let mem: &mut LinearMemory = &mut memories[init.memory_index.index()];
|
||||
// let end_of_init = offset + init.data.len();
|
||||
// if end_of_init > mem.current_size() {
|
||||
// let grow_pages = (end_of_init / LinearMemory::PAGE_SIZE as usize) + 1;
|
||||
// mem.grow(grow_pages as u32)
|
||||
// .expect("failed to grow memory for data initializers");
|
||||
// }
|
||||
let to_init = &mut mem[offset..offset + init.data.len()];
|
||||
to_init.copy_from_slice(&init.data);
|
||||
}
|
||||
|
||||
memories
|
||||
.iter_mut()
|
||||
.map(|mem| mem.into_vm_memory())
|
||||
.collect::<Vec<_>>()
|
||||
.into_boxed_slice()
|
||||
}
|
||||
|
||||
fn generate_tables(module: &Module) -> Box<[TableBacking]> {
|
||||
let mut tables = Vec::with_capacity(module.tables.len());
|
||||
|
||||
for (_, table) in &module.tables {
|
||||
let table_backing = TableBacking::new(table);
|
||||
tables.push(table_backing);
|
||||
}
|
||||
|
||||
tables.into_boxed_slice()
|
||||
}
|
||||
|
||||
fn finalize_tables(
|
||||
module: &Module,
|
||||
imports: &ImportBacking,
|
||||
tables: &mut [TableBacking],
|
||||
) -> Box<[vm::LocalTable]> {
|
||||
for init in &module.table_initializers {
|
||||
assert!(init.base.is_none(), "global base not supported yet");
|
||||
let table = &mut tables[init.table_index.index()];
|
||||
match table.elements {
|
||||
TableElements::Anyfunc(ref mut elements) => {
|
||||
for (i, &func_index) in init.elements.iter().enumerate() {
|
||||
|
||||
let sig_index = module.func_assoc[func_index];
|
||||
let vm_sig_id = vm::SigId(sig_index.index() as u32);
|
||||
|
||||
let func_data = if module.is_imported_function(func_index) {
|
||||
imports.functions[func_index.index()].clone()
|
||||
} else {
|
||||
vm::ImportedFunc {
|
||||
func: module
|
||||
.func_resolver
|
||||
.get(module, func_index)
|
||||
.unwrap()
|
||||
.as_ptr(),
|
||||
}
|
||||
};
|
||||
|
||||
elements[init.offset + i] = vm::Anyfunc {
|
||||
func_data,
|
||||
sig_id: vm_sig_id,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tables
|
||||
.iter_mut()
|
||||
.map(|table| table.into_vm_table())
|
||||
.collect::<Vec<_>>()
|
||||
.into_boxed_slice()
|
||||
}
|
||||
|
||||
fn generate_globals(module: &Module) -> Box<[vm::LocalGlobal]> {
|
||||
let globals = vec![vm::LocalGlobal::null(); module.globals.len()];
|
||||
|
||||
globals.into_boxed_slice()
|
||||
}
|
||||
|
||||
fn finalize_globals(
|
||||
module: &Module,
|
||||
imports: &ImportBacking,
|
||||
mut globals: Box<[vm::LocalGlobal]>,
|
||||
) -> Box<[vm::LocalGlobal]> {
|
||||
for (to, (_, from)) in globals.iter_mut().zip(module.globals.into_iter()) {
|
||||
to.data = match from.init {
|
||||
Initializer::Const(Value::I32(x)) => x as u64,
|
||||
Initializer::Const(Value::I64(x)) => x as u64,
|
||||
Initializer::Const(Value::F32(x)) => x as u64,
|
||||
Initializer::Const(Value::F64(x)) => x,
|
||||
Initializer::GetGlobal(index) => (imports.globals[index.index()].global).data,
|
||||
};
|
||||
}
|
||||
|
||||
globals
|
||||
}
|
||||
|
||||
// fn generate_tables(module: &Module, _options: &InstanceOptions) -> (Box<[TableBacking]>, Box<[vm::LocalTable]>) {
|
||||
// let mut tables = Vec::new();
|
||||
// // Reserve space for tables
|
||||
// tables.reserve_exact(module.info.tables.len());
|
||||
|
||||
// // Get tables in module
|
||||
// for table in &module.info.tables {
|
||||
// let table: Vec<usize> = match table.import_name.as_ref() {
|
||||
// Some((module_name, field_name)) => {
|
||||
// let imported =
|
||||
// import_object.get(&module_name.as_str(), &field_name.as_str());
|
||||
// match imported {
|
||||
// Some(ImportValue::Table(t)) => t.to_vec(),
|
||||
// None => {
|
||||
// if options.mock_missing_tables {
|
||||
// debug!(
|
||||
// "The Imported table {}.{} is not provided, therefore will be mocked.",
|
||||
// module_name, field_name
|
||||
// );
|
||||
// let len = table.entity.minimum as usize;
|
||||
// let mut v = Vec::with_capacity(len);
|
||||
// v.resize(len, 0);
|
||||
// v
|
||||
// } else {
|
||||
// panic!(
|
||||
// "Imported table value was not provided ({}.{})",
|
||||
// module_name, field_name
|
||||
// )
|
||||
// }
|
||||
// }
|
||||
// _ => panic!(
|
||||
// "Expected global table, but received {:?} ({}.{})",
|
||||
// imported, module_name, field_name
|
||||
// ),
|
||||
// }
|
||||
// }
|
||||
// None => {
|
||||
// let len = table.entity.minimum as usize;
|
||||
// let mut v = Vec::with_capacity(len);
|
||||
// v.resize(len, 0);
|
||||
// v
|
||||
// }
|
||||
// };
|
||||
// tables.push(table);
|
||||
// }
|
||||
|
||||
// // instantiate tables
|
||||
// for table_element in &module.info.table_elements {
|
||||
// let base = match table_element.base {
|
||||
// Some(global_index) => globals_data[global_index.index()] as usize,
|
||||
// None => 0,
|
||||
// };
|
||||
|
||||
// let table = &mut tables[table_element.table_index.index()];
|
||||
// for (i, func_index) in table_element.elements.iter().enumerate() {
|
||||
// // since the table just contains functions in the MVP
|
||||
// // we get the address of the specified function indexes
|
||||
// // to populate the table.
|
||||
|
||||
// // let func_index = *elem_index - module.info.imported_funcs.len() as u32;
|
||||
// // let func_addr = functions[func_index.index()].as_ptr();
|
||||
// let func_addr = get_function_addr(&func_index, &import_functions, &functions);
|
||||
// table[base + table_element.offset + i] = func_addr as _;
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct ImportBacking {
|
||||
pub functions: Box<[vm::ImportedFunc]>,
|
||||
pub memories: Box<[vm::ImportedMemory]>,
|
||||
pub tables: Box<[vm::ImportedTable]>,
|
||||
pub globals: Box<[vm::ImportedGlobal]>,
|
||||
}
|
||||
|
||||
impl ImportBacking {
|
||||
pub fn new(module: &Module, imports: &dyn ImportResolver) -> Result<Self, String> {
|
||||
assert!(
|
||||
module.imported_memories.len() == 0,
|
||||
"imported memories not yet supported"
|
||||
);
|
||||
assert!(
|
||||
module.imported_tables.len() == 0,
|
||||
"imported tables not yet supported"
|
||||
);
|
||||
|
||||
let mut functions = Vec::with_capacity(module.imported_functions.len());
|
||||
for (
|
||||
index,
|
||||
ImportName {
|
||||
module: mod_name,
|
||||
name: item_name,
|
||||
},
|
||||
) in &module.imported_functions
|
||||
{
|
||||
let sig_index = module.func_assoc[index];
|
||||
let expected_sig = module.sig_registry.lookup_func_sig(sig_index);
|
||||
let import = imports.get(mod_name, item_name);
|
||||
if let Some(&Import::Func(func, ref signature)) = import {
|
||||
if expected_sig == signature {
|
||||
functions.push(vm::ImportedFunc {
|
||||
func,
|
||||
// vmctx: ptr::null_mut(),
|
||||
});
|
||||
} else {
|
||||
return Err(format!(
|
||||
"unexpected signature for {:?}:{:?}",
|
||||
mod_name, item_name
|
||||
));
|
||||
}
|
||||
} else {
|
||||
return Err(format!("incorrect type for {:?}:{:?}", mod_name, item_name));
|
||||
}
|
||||
}
|
||||
|
||||
let mut globals = Vec::with_capacity(module.imported_globals.len());
|
||||
for (
|
||||
_,
|
||||
(
|
||||
ImportName {
|
||||
module: mod_name,
|
||||
name: item_name,
|
||||
},
|
||||
global_desc,
|
||||
),
|
||||
) in &module.imported_globals
|
||||
{
|
||||
let import = imports.get(mod_name, item_name);
|
||||
if let Some(&Import::Global(val)) = import {
|
||||
if val.ty() == global_desc.ty {
|
||||
globals.push(vm::ImportedGlobal {
|
||||
global: vm::LocalGlobal {
|
||||
data: match val {
|
||||
Value::I32(n) => n as u64,
|
||||
Value::I64(n) => n as u64,
|
||||
Value::F32(n) => n as u64,
|
||||
Value::F64(n) => n,
|
||||
},
|
||||
},
|
||||
});
|
||||
} else {
|
||||
return Err(format!(
|
||||
"unexpected global type for {:?}:{:?}",
|
||||
mod_name, item_name
|
||||
));
|
||||
}
|
||||
} else {
|
||||
return Err(format!("incorrect type for {:?}:{:?}", mod_name, item_name));
|
||||
}
|
||||
}
|
||||
|
||||
Ok(ImportBacking {
|
||||
functions: functions.into_boxed_slice(),
|
||||
memories: vec![].into_boxed_slice(),
|
||||
tables: vec![].into_boxed_slice(),
|
||||
globals: globals.into_boxed_slice(),
|
||||
})
|
||||
}
|
||||
}
|
174
lib/runtime/src/instance.rs
Normal file
174
lib/runtime/src/instance.rs
Normal file
@ -0,0 +1,174 @@
|
||||
use crate::recovery::call_protected;
|
||||
use crate::{
|
||||
backing::{ImportBacking, LocalBacking},
|
||||
memory::LinearMemory,
|
||||
module::{Export, Module},
|
||||
table::TableBacking,
|
||||
types::{FuncIndex, FuncSig, Memory, Table, Type, Value},
|
||||
vm,
|
||||
};
|
||||
use hashbrown::HashMap;
|
||||
use libffi::high::{arg as libffi_arg, call as libffi_call, CodePtr};
|
||||
use std::iter;
|
||||
use std::sync::Arc;
|
||||
|
||||
pub struct Instance {
|
||||
pub(crate) backing: LocalBacking,
|
||||
import_backing: ImportBacking,
|
||||
pub module: Module,
|
||||
}
|
||||
|
||||
impl Instance {
|
||||
pub(crate) fn new(module: Module, imports: &dyn ImportResolver) -> Result<Box<Instance>, String> {
|
||||
let import_backing = ImportBacking::new(&module, imports)?;
|
||||
let backing = LocalBacking::new(&module, &import_backing);
|
||||
|
||||
let start_func = module.start_func;
|
||||
|
||||
let mut instance = Box::new(Instance {
|
||||
backing,
|
||||
import_backing,
|
||||
module,
|
||||
});
|
||||
|
||||
if let Some(start_index) = start_func {
|
||||
instance.call_with_index(start_index, &[])?;
|
||||
}
|
||||
|
||||
Ok(instance)
|
||||
}
|
||||
|
||||
/// Call an exported webassembly function given the export name.
|
||||
/// Pass arguments by wrapping each one in the `Val` enum.
|
||||
/// The returned value is also returned in a `Val`.
|
||||
///
|
||||
/// This will eventually return `Result<Option<Vec<Val>>, String>` in
|
||||
/// order to support multi-value returns.
|
||||
pub fn call(&mut self, name: &str, args: &[Value]) -> Result<Option<Value>, String> {
|
||||
let func_index = *self
|
||||
.module
|
||||
.exports
|
||||
.get(name)
|
||||
.ok_or_else(|| "there is no export with that name".to_string())
|
||||
.and_then(|export| match export {
|
||||
Export::Func(func_index) => Ok(func_index),
|
||||
_ => Err("that export is not a function".to_string()),
|
||||
})?;
|
||||
|
||||
self.call_with_index(func_index, args)
|
||||
}
|
||||
|
||||
fn call_with_index(
|
||||
&mut self,
|
||||
func_index: FuncIndex,
|
||||
args: &[Value],
|
||||
) -> Result<Option<Value>, String> {
|
||||
// Check the function signature.
|
||||
let sig_index = *self
|
||||
.module
|
||||
.func_assoc
|
||||
.get(func_index)
|
||||
.expect("broken invariant, incorrect func index");
|
||||
|
||||
{
|
||||
let signature = self.module.sig_registry.lookup_func_sig(sig_index);
|
||||
|
||||
assert!(
|
||||
signature.returns.len() <= 1,
|
||||
"multi-value returns not yet supported"
|
||||
);
|
||||
|
||||
if !signature.check_sig(args) {
|
||||
return Err("incorrect signature".to_string());
|
||||
}
|
||||
}
|
||||
|
||||
// the vmctx will be located at the same place on the stack the entire time that this
|
||||
// wasm function is running.
|
||||
let mut vmctx = vm::Ctx::new(
|
||||
&mut self.backing,
|
||||
&mut self.import_backing,
|
||||
);
|
||||
let vmctx_ptr = &mut vmctx as *mut vm::Ctx;
|
||||
|
||||
let libffi_args: Vec<_> = args
|
||||
.iter()
|
||||
.map(|val| match val {
|
||||
Value::I32(ref x) => libffi_arg(x),
|
||||
Value::I64(ref x) => libffi_arg(x),
|
||||
Value::F32(ref x) => libffi_arg(x),
|
||||
Value::F64(ref x) => libffi_arg(x),
|
||||
})
|
||||
.chain(iter::once(libffi_arg(&vmctx_ptr)))
|
||||
.collect();
|
||||
|
||||
let func_ptr = CodePtr::from_ptr(
|
||||
self.module
|
||||
.func_resolver
|
||||
.get(&self.module, func_index)
|
||||
.expect("broken invariant, func resolver not synced with module.exports")
|
||||
.cast()
|
||||
.as_ptr(),
|
||||
);
|
||||
|
||||
call_protected(|| {
|
||||
self.module.sig_registry.lookup_func_sig(sig_index)
|
||||
.returns
|
||||
.first()
|
||||
.map(|ty| match ty {
|
||||
Type::I32 => Value::I32(unsafe { libffi_call(func_ptr, &libffi_args) }),
|
||||
Type::I64 => Value::I64(unsafe { libffi_call(func_ptr, &libffi_args) }),
|
||||
Type::F32 => Value::F32(unsafe { libffi_call(func_ptr, &libffi_args) }),
|
||||
Type::F64 => Value::F64(unsafe { libffi_call(func_ptr, &libffi_args) }),
|
||||
})
|
||||
.or_else(|| {
|
||||
// call with no returns
|
||||
unsafe {
|
||||
libffi_call::<()>(func_ptr, &libffi_args);
|
||||
}
|
||||
None
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Import {
|
||||
Func(*const vm::Func, FuncSig),
|
||||
Table(Arc<TableBacking>, Table),
|
||||
Memory(Arc<LinearMemory>, Memory),
|
||||
Global(Value),
|
||||
}
|
||||
|
||||
pub struct Imports {
|
||||
map: HashMap<String, HashMap<String, Import>>,
|
||||
}
|
||||
|
||||
impl Imports {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
map: HashMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn add(&mut self, module: String, name: String, import: Import) {
|
||||
self.map
|
||||
.entry(module)
|
||||
.or_insert(HashMap::new())
|
||||
.insert(name, import);
|
||||
}
|
||||
|
||||
pub fn get(&self, module: &str, name: &str) -> Option<&Import> {
|
||||
self.map.get(module).and_then(|m| m.get(name))
|
||||
}
|
||||
}
|
||||
|
||||
impl ImportResolver for Imports {
|
||||
fn get(&self, module: &str, name: &str) -> Option<&Import> {
|
||||
self.get(module, name)
|
||||
}
|
||||
}
|
||||
|
||||
pub trait ImportResolver {
|
||||
fn get(&self, module: &str, name: &str) -> Option<&Import>;
|
||||
}
|
29
lib/runtime/src/lib.rs
Normal file
29
lib/runtime/src/lib.rs
Normal file
@ -0,0 +1,29 @@
|
||||
#[macro_use]
|
||||
mod macros;
|
||||
mod backend;
|
||||
mod backing;
|
||||
mod instance;
|
||||
mod memory;
|
||||
mod module;
|
||||
mod sig_registry;
|
||||
mod table;
|
||||
mod recovery;
|
||||
mod sighandler;
|
||||
mod mmap;
|
||||
pub mod types;
|
||||
pub mod vm;
|
||||
pub mod vmcalls;
|
||||
|
||||
pub use self::backend::{Compiler, FuncResolver};
|
||||
pub use self::instance::{Import, ImportResolver, Imports, Instance};
|
||||
pub use self::module::Module;
|
||||
pub use self::sig_registry::SigRegistry;
|
||||
pub use self::memory::LinearMemory;
|
||||
|
||||
/// Compile a webassembly module using the provided compiler.
|
||||
pub fn compile(
|
||||
wasm: &[u8],
|
||||
compiler: &dyn Compiler,
|
||||
) -> Result<Module, String> {
|
||||
compiler.compile(wasm)
|
||||
}
|
5
lib/runtime/src/macros.rs
Normal file
5
lib/runtime/src/macros.rs
Normal file
@ -0,0 +1,5 @@
|
||||
#[macro_export]
|
||||
macro_rules! debug {
|
||||
($fmt:expr) => (if cfg!(any(debug_assertions, feature="debug")) { println!(concat!("wasmer-runtime(:{})::", $fmt), line!()) });
|
||||
($fmt:expr, $($arg:tt)*) => (if cfg!(any(debug_assertions, feature="debug")) { println!(concat!("wasmer-runtime(:{})::", $fmt, "\n"), line!(), $($arg)*) });
|
||||
}
|
225
lib/runtime/src/memory.rs
Normal file
225
lib/runtime/src/memory.rs
Normal file
@ -0,0 +1,225 @@
|
||||
//! The webassembly::Memory() constructor creates a new Memory object which is
|
||||
//! a structure that holds the raw bytes of memory accessed by a
|
||||
//! webassembly::Instance.
|
||||
//! A memory created by Rust or in WebAssembly code will be accessible and
|
||||
//! mutable from both Rust and WebAssembly.
|
||||
use std::ops::{Deref, DerefMut};
|
||||
|
||||
use crate::{types::Memory, vm::LocalMemory, mmap::{Mmap, Protect}};
|
||||
|
||||
/// A linear memory instance.
|
||||
#[derive(Debug)]
|
||||
pub struct LinearMemory {
|
||||
/// The actual memory allocation.
|
||||
mmap: Mmap,
|
||||
|
||||
/// The current number of wasm pages.
|
||||
current: u32,
|
||||
|
||||
// The maximum size the WebAssembly Memory is allowed to grow
|
||||
// to, in units of WebAssembly pages. When present, the maximum
|
||||
// parameter acts as a hint to the engine to reserve memory up
|
||||
// front. However, the engine may ignore or clamp this reservation
|
||||
// request. In general, most WebAssembly modules shouldn't need
|
||||
// to set a maximum.
|
||||
max: Option<u32>,
|
||||
|
||||
// The size of the extra guard pages after the end.
|
||||
// Is used to optimize loads and stores with constant offsets.
|
||||
offset_guard_size: usize,
|
||||
|
||||
/// Requires exception catching to handle out-of-bounds accesses.
|
||||
requires_signal_catch: bool,
|
||||
}
|
||||
|
||||
/// It holds the raw bytes of memory accessed by a WebAssembly Instance
|
||||
impl LinearMemory {
|
||||
pub const PAGE_SIZE: u32 = 65_536;
|
||||
pub const MAX_PAGES: u32 = 65_536;
|
||||
pub const DEFAULT_HEAP_SIZE: usize = 1 << 32; // 4 GiB
|
||||
pub const DEFAULT_GUARD_SIZE: usize = 1 << 31; // 2 GiB
|
||||
pub const DEFAULT_SIZE: usize = Self::DEFAULT_HEAP_SIZE + Self::DEFAULT_GUARD_SIZE; // 6 GiB
|
||||
|
||||
/// Create a new linear memory instance with specified initial and maximum number of pages.
|
||||
///
|
||||
/// `maximum` cannot be set to more than `65536` pages.
|
||||
pub fn new(mem: &Memory) -> Self {
|
||||
assert!(mem.min <= Self::MAX_PAGES);
|
||||
assert!(mem.max.is_none() || mem.max.unwrap() <= Self::MAX_PAGES);
|
||||
debug!("Instantiate LinearMemory(mem: {:?})", mem);
|
||||
|
||||
let (mmap_size, initial_pages, offset_guard_size, requires_signal_catch) =
|
||||
if mem.is_static_heap() {
|
||||
(Self::DEFAULT_SIZE, mem.min, Self::DEFAULT_GUARD_SIZE, true)
|
||||
// This is a static heap
|
||||
} else {
|
||||
// this is a dynamic heap
|
||||
assert!(!mem.shared, "shared memories must have a maximum size.");
|
||||
|
||||
(
|
||||
mem.min as usize * Self::PAGE_SIZE as usize,
|
||||
mem.min,
|
||||
0,
|
||||
false,
|
||||
)
|
||||
};
|
||||
|
||||
let mut mmap = Mmap::with_size(mmap_size).unwrap();
|
||||
|
||||
// map initial pages as readwrite since the inital mmap is mapped as not accessible.
|
||||
if initial_pages != 0 {
|
||||
unsafe {
|
||||
mmap.protect(0..(initial_pages as usize * Self::PAGE_SIZE as usize), Protect::ReadWrite)
|
||||
.expect("unable to make memory accessible");
|
||||
}
|
||||
}
|
||||
|
||||
Self {
|
||||
mmap,
|
||||
current: initial_pages,
|
||||
max: mem.max,
|
||||
offset_guard_size,
|
||||
requires_signal_catch,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns an base address of this linear memory.
|
||||
pub fn base(&mut self) -> *mut u8 {
|
||||
self.mmap.as_ptr()
|
||||
}
|
||||
|
||||
/// Returns a number of allocated wasm pages.
|
||||
pub fn current_size(&self) -> usize {
|
||||
self.current as usize * Self::PAGE_SIZE as usize
|
||||
}
|
||||
|
||||
pub fn current_pages(&self) -> u32 {
|
||||
self.current
|
||||
}
|
||||
|
||||
/// Returns the maximum number of wasm pages allowed.
|
||||
pub fn maximum_size(&self) -> u32 {
|
||||
self.max.unwrap_or(Self::MAX_PAGES)
|
||||
}
|
||||
|
||||
pub fn into_vm_memory(&mut self) -> LocalMemory {
|
||||
LocalMemory {
|
||||
base: self.base(),
|
||||
size: self.current_size(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Grow memory by the specified amount of pages.
|
||||
///
|
||||
/// Returns `None` if memory can't be grown by the specified amount
|
||||
/// of pages.
|
||||
pub fn grow_dynamic(&mut self, add_pages: u32) -> Option<i32> {
|
||||
debug!("grow_memory_dynamic called!");
|
||||
assert!(self.max.is_none());
|
||||
if add_pages == 0 {
|
||||
return Some(self.current as _);
|
||||
}
|
||||
|
||||
let prev_pages = self.current;
|
||||
|
||||
let new_pages = match self.current.checked_add(add_pages) {
|
||||
Some(new_pages) => new_pages,
|
||||
None => return None,
|
||||
};
|
||||
|
||||
if let Some(val) = self.max {
|
||||
if new_pages > val {
|
||||
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.
|
||||
} else if new_pages >= Self::MAX_PAGES {
|
||||
return None;
|
||||
}
|
||||
|
||||
let new_bytes = (new_pages * Self::PAGE_SIZE) as usize;
|
||||
|
||||
if new_bytes > self.mmap.size() - self.offset_guard_size {
|
||||
let mmap_size = new_bytes.checked_add(self.offset_guard_size)?;
|
||||
let mut new_mmap = Mmap::with_size(mmap_size).ok()?;
|
||||
|
||||
unsafe {
|
||||
new_mmap.protect(0..new_bytes, Protect::ReadWrite).ok()?;
|
||||
}
|
||||
|
||||
let copy_size = self.mmap.size() - self.offset_guard_size;
|
||||
unsafe {
|
||||
new_mmap.as_slice_mut()[..copy_size]
|
||||
.copy_from_slice(&self.mmap.as_slice()[..copy_size]);
|
||||
}
|
||||
|
||||
self.mmap = new_mmap;
|
||||
}
|
||||
|
||||
self.current = new_pages;
|
||||
|
||||
Some(prev_pages as i32)
|
||||
}
|
||||
|
||||
pub fn grow_static(&mut self, add_pages: u32) -> Option<i32> {
|
||||
debug!("grow_memory_static called!");
|
||||
assert!(self.max.is_some());
|
||||
if add_pages == 0 {
|
||||
return Some(self.current as _);
|
||||
}
|
||||
|
||||
let prev_pages = self.current;
|
||||
|
||||
let new_pages = match self.current.checked_add(add_pages) {
|
||||
Some(new_pages) => new_pages,
|
||||
None => return None,
|
||||
};
|
||||
|
||||
if let Some(val) = self.max {
|
||||
if new_pages > val {
|
||||
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.
|
||||
} else if new_pages >= Self::MAX_PAGES {
|
||||
return None;
|
||||
}
|
||||
|
||||
let prev_bytes = (prev_pages * Self::PAGE_SIZE) as usize;
|
||||
let new_bytes = (new_pages * Self::PAGE_SIZE) as usize;
|
||||
|
||||
unsafe {
|
||||
self.mmap.protect(prev_bytes..new_bytes, Protect::ReadWrite).ok()?;
|
||||
}
|
||||
|
||||
self.current = new_pages;
|
||||
|
||||
Some(prev_pages as i32)
|
||||
}
|
||||
}
|
||||
|
||||
// Not comparing based on memory content. That would be inefficient.
|
||||
impl PartialEq for LinearMemory {
|
||||
fn eq(&self, other: &LinearMemory) -> bool {
|
||||
self.current == other.current && self.max == other.max
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for LinearMemory {
|
||||
type Target = [u8];
|
||||
fn deref(&self) -> &[u8] {
|
||||
unsafe {
|
||||
self.mmap.as_slice()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl DerefMut for LinearMemory {
|
||||
fn deref_mut(&mut self) -> &mut [u8] {
|
||||
unsafe {
|
||||
self.mmap.as_slice_mut()
|
||||
}
|
||||
}
|
||||
}
|
103
lib/runtime/src/mmap.rs
Normal file
103
lib/runtime/src/mmap.rs
Normal file
@ -0,0 +1,103 @@
|
||||
use std::{slice, ptr};
|
||||
use std::ops::Range;
|
||||
use nix::libc;
|
||||
use page_size;
|
||||
use errno;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Mmap {
|
||||
ptr: *mut u8,
|
||||
size: usize,
|
||||
}
|
||||
|
||||
impl Mmap {
|
||||
pub fn with_size(size: usize) -> Result<Self, String> {
|
||||
if size == 0 {
|
||||
return Ok(Self {
|
||||
ptr: ptr::null_mut(),
|
||||
size: 0,
|
||||
});
|
||||
}
|
||||
|
||||
let size = round_up_to_page_size(size, page_size::get());
|
||||
|
||||
let ptr = unsafe {
|
||||
libc::mmap(
|
||||
ptr::null_mut(),
|
||||
size,
|
||||
libc::PROT_NONE,
|
||||
libc::MAP_PRIVATE | libc::MAP_ANON,
|
||||
-1,
|
||||
0,
|
||||
)
|
||||
};
|
||||
|
||||
if ptr == -1 as _ {
|
||||
Err(errno::errno().to_string())
|
||||
} else {
|
||||
Ok(Self {
|
||||
ptr: ptr as *mut u8,
|
||||
size,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub unsafe fn protect(&mut self, range: Range<usize>, protect: Protect) -> Result<(), String> {
|
||||
let page_size = page_size::get();
|
||||
let start = self.ptr.add(round_down_to_page_size(range.start, page_size));
|
||||
let size = range.end - range.start;
|
||||
|
||||
let success = libc::mprotect(start as _, size, protect as i32);
|
||||
if success == -1 {
|
||||
Err(errno::errno().to_string())
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn size(&self) -> usize {
|
||||
self.size
|
||||
}
|
||||
|
||||
pub unsafe fn as_slice(&self) -> &[u8] {
|
||||
slice::from_raw_parts(self.ptr, self.size)
|
||||
}
|
||||
|
||||
pub unsafe fn as_slice_mut(&mut self) -> &mut [u8] {
|
||||
slice::from_raw_parts_mut(self.ptr, self.size)
|
||||
}
|
||||
|
||||
pub fn as_ptr(&mut self) -> *mut u8 {
|
||||
self.ptr
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for Mmap {
|
||||
fn drop(&mut self) {
|
||||
if !self.ptr.is_null() {
|
||||
let success = unsafe { libc::munmap(self.ptr as _, self.size) };
|
||||
assert_eq!(success, 0, "failed to unmap memory: {}", errno::errno());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||||
#[allow(dead_code)]
|
||||
pub enum Protect {
|
||||
None = 0,
|
||||
Read = 1,
|
||||
Write = 2,
|
||||
ReadWrite = 1 | 2,
|
||||
Exec = 4,
|
||||
ReadExec = 1 | 4,
|
||||
}
|
||||
|
||||
/// 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)
|
||||
}
|
||||
|
||||
/// Round `size` down to the nearest multiple of `page_size`.
|
||||
fn round_down_to_page_size(size: usize, page_size: usize) -> usize {
|
||||
size & !(page_size-1)
|
||||
}
|
113
lib/runtime/src/module.rs
Normal file
113
lib/runtime/src/module.rs
Normal file
@ -0,0 +1,113 @@
|
||||
use crate::{
|
||||
backend::FuncResolver,
|
||||
types::{
|
||||
FuncIndex, Global, GlobalDesc, GlobalIndex, Map, MapIndex, Memory, MemoryIndex,
|
||||
SigIndex, Table, TableIndex,
|
||||
},
|
||||
sig_registry::SigRegistry,
|
||||
ImportResolver,
|
||||
Instance,
|
||||
};
|
||||
use std::sync::Arc;
|
||||
use std::ops::Deref;
|
||||
use hashbrown::HashMap;
|
||||
|
||||
/// This is used to instantiate a new webassembly module.
|
||||
pub struct ModuleInner {
|
||||
pub func_resolver: Box<dyn FuncResolver>,
|
||||
pub memories: Map<MemoryIndex, Memory>,
|
||||
pub globals: Map<GlobalIndex, Global>,
|
||||
pub tables: Map<TableIndex, Table>,
|
||||
|
||||
pub imported_functions: Map<FuncIndex, ImportName>,
|
||||
pub imported_memories: Map<MemoryIndex, (ImportName, Memory)>,
|
||||
pub imported_tables: Map<TableIndex, (ImportName, Table)>,
|
||||
pub imported_globals: Map<GlobalIndex, (ImportName, GlobalDesc)>,
|
||||
|
||||
pub exports: HashMap<String, Export>,
|
||||
|
||||
pub data_initializers: Vec<DataInitializer>,
|
||||
pub table_initializers: Vec<TableInitializer>,
|
||||
pub start_func: Option<FuncIndex>,
|
||||
|
||||
pub func_assoc: Map<FuncIndex, SigIndex>,
|
||||
pub sig_registry: SigRegistry,
|
||||
}
|
||||
|
||||
pub struct Module(Arc<ModuleInner>);
|
||||
|
||||
impl Module {
|
||||
#[inline]
|
||||
pub fn new(inner: ModuleInner) -> Self {
|
||||
Module(Arc::new(inner))
|
||||
}
|
||||
|
||||
/// Instantiate a webassembly module with the provided imports.
|
||||
pub fn instantiate(&self, imports: &dyn ImportResolver) -> Result<Box<Instance>, String> {
|
||||
Instance::new(Module(Arc::clone(&self.0)), imports)
|
||||
}
|
||||
}
|
||||
|
||||
impl ModuleInner {
|
||||
pub(crate) fn is_imported_function(&self, func_index: FuncIndex) -> bool {
|
||||
func_index.index() < self.imported_functions.len()
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for Module {
|
||||
type Target = ModuleInner;
|
||||
|
||||
fn deref(&self) -> &ModuleInner {
|
||||
&*self.0
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ImportName {
|
||||
pub module: String,
|
||||
pub name: String,
|
||||
}
|
||||
|
||||
impl From<(String, String)> for ImportName {
|
||||
fn from(n: (String, String)) -> Self {
|
||||
ImportName {
|
||||
module: n.0,
|
||||
name: n.1,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum Export {
|
||||
Func(FuncIndex),
|
||||
Memory(MemoryIndex),
|
||||
Global(GlobalIndex),
|
||||
Table(TableIndex),
|
||||
}
|
||||
|
||||
/// A data initializer for linear memory.
|
||||
#[derive(Debug)]
|
||||
pub struct DataInitializer {
|
||||
/// The index of the memory to initialize.
|
||||
pub memory_index: MemoryIndex,
|
||||
/// Optionally a globalvalue base to initialize at.
|
||||
pub base: Option<GlobalIndex>,
|
||||
/// A constant offset to initialize at.
|
||||
pub offset: usize,
|
||||
/// The initialization data.
|
||||
pub data: Vec<u8>,
|
||||
}
|
||||
|
||||
/// A WebAssembly table initializer.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct TableInitializer {
|
||||
/// The index of a table to initialize.
|
||||
pub table_index: TableIndex,
|
||||
/// Optionally, a global variable giving a base index.
|
||||
pub base: Option<GlobalIndex>,
|
||||
/// The offset to add to the base.
|
||||
pub offset: usize,
|
||||
/// The values to write into the table elements.
|
||||
pub elements: Vec<FuncIndex>,
|
||||
}
|
72
lib/runtime/src/recovery.rs
Normal file
72
lib/runtime/src/recovery.rs
Normal file
@ -0,0 +1,72 @@
|
||||
//! When a WebAssembly module triggers any traps, we perform recovery here.
|
||||
//!
|
||||
//! This module uses TLS (thread-local storage) to track recovery information. Since the four signals we're handling
|
||||
//! are very special, the async signal unsafety of Rust's TLS implementation generally does not affect the correctness here
|
||||
//! unless you have memory unsafety elsewhere in your code.
|
||||
|
||||
use crate::sighandler::install_sighandler;
|
||||
use nix::libc::siginfo_t;
|
||||
use nix::sys::signal::{Signal, SIGBUS, SIGFPE, SIGILL, SIGSEGV};
|
||||
use std::cell::{Cell, UnsafeCell};
|
||||
use std::sync::Once;
|
||||
|
||||
extern "C" {
|
||||
pub fn setjmp(env: *mut ::nix::libc::c_void) -> ::nix::libc::c_int;
|
||||
fn longjmp(env: *mut ::nix::libc::c_void, val: ::nix::libc::c_int) -> !;
|
||||
}
|
||||
|
||||
const SETJMP_BUFFER_LEN: usize = 27;
|
||||
pub static SIGHANDLER_INIT: Once = Once::new();
|
||||
|
||||
thread_local! {
|
||||
pub static SETJMP_BUFFER: UnsafeCell<[::nix::libc::c_int; SETJMP_BUFFER_LEN]> = UnsafeCell::new([0; SETJMP_BUFFER_LEN]);
|
||||
pub static CAUGHT_ADDRESS: Cell<usize> = Cell::new(0);
|
||||
}
|
||||
|
||||
pub fn call_protected<T>(f: impl FnOnce() -> T) -> Result<T, String> {
|
||||
unsafe {
|
||||
let jmp_buf = SETJMP_BUFFER.with(|buf| buf.get());
|
||||
let prev_jmp_buf = *jmp_buf;
|
||||
|
||||
SIGHANDLER_INIT.call_once(|| {
|
||||
install_sighandler();
|
||||
});
|
||||
|
||||
let signum = setjmp(jmp_buf as *mut ::nix::libc::c_void);
|
||||
if signum != 0 {
|
||||
*jmp_buf = prev_jmp_buf;
|
||||
let addr = CAUGHT_ADDRESS.with(|cell| cell.get());
|
||||
|
||||
let signal = match Signal::from_c_int(signum) {
|
||||
Ok(SIGFPE) => "floating-point exception",
|
||||
Ok(SIGILL) => "illegal instruction",
|
||||
Ok(SIGSEGV) => "segmentation violation",
|
||||
Ok(SIGBUS) => "bus error",
|
||||
Err(_) => "error while getting the Signal",
|
||||
_ => "unkown trapped signal",
|
||||
};
|
||||
Err(format!("trap at {:#x} - {}", addr, signal))
|
||||
} else {
|
||||
let ret = f(); // TODO: Switch stack?
|
||||
*jmp_buf = prev_jmp_buf;
|
||||
Ok(ret)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Unwinds to last protected_call.
|
||||
pub unsafe fn do_unwind(signum: i32, siginfo: *mut siginfo_t) -> ! {
|
||||
// Since do_unwind is only expected to get called from WebAssembly code which doesn't hold any host resources (locks etc.)
|
||||
// itself, accessing TLS here is safe. In case any other code calls this, it often indicates a memory safety bug and you should
|
||||
// temporarily disable the signal handlers to debug it.
|
||||
|
||||
let jmp_buf = SETJMP_BUFFER.with(|buf| buf.get());
|
||||
if *jmp_buf == [0; SETJMP_BUFFER_LEN] {
|
||||
::std::process::abort();
|
||||
}
|
||||
// We only target macos at the moment as other ones might not have si_addr field
|
||||
#[cfg(target_os = "macos")]
|
||||
CAUGHT_ADDRESS.with(|cell| cell.set((*siginfo).si_addr as _));
|
||||
|
||||
longjmp(jmp_buf as *mut ::nix::libc::c_void, signum)
|
||||
}
|
36
lib/runtime/src/sig_registry.rs
Normal file
36
lib/runtime/src/sig_registry.rs
Normal file
@ -0,0 +1,36 @@
|
||||
use crate::{
|
||||
types::{FuncSig, Map, SigIndex, MapIndex},
|
||||
vm,
|
||||
};
|
||||
use hashbrown::HashMap;
|
||||
|
||||
pub struct SigRegistry {
|
||||
func_table: HashMap<FuncSig, SigIndex>,
|
||||
sig_assoc: Map<SigIndex, FuncSig>,
|
||||
}
|
||||
|
||||
impl SigRegistry {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
func_table: HashMap::new(),
|
||||
sig_assoc: Map::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn register(&mut self, func_sig: FuncSig) -> SigIndex {
|
||||
let func_table = &mut self.func_table;
|
||||
let sig_assoc = &mut self.sig_assoc;
|
||||
*func_table.entry(func_sig.clone()).or_insert_with(|| {
|
||||
sig_assoc.push(func_sig)
|
||||
})
|
||||
}
|
||||
|
||||
pub fn lookup_func_sig(&self, sig_index: SigIndex) -> &FuncSig {
|
||||
&self.sig_assoc[sig_index]
|
||||
}
|
||||
|
||||
pub(crate) fn into_vm_sigid(&self) -> Box<[vm::SigId]> {
|
||||
let v: Vec<_> = self.sig_assoc.iter().map(|(sig_index, _)| vm::SigId(sig_index.index() as u32)).collect();
|
||||
v.into_boxed_slice()
|
||||
}
|
||||
}
|
33
lib/runtime/src/sighandler.rs
Normal file
33
lib/runtime/src/sighandler.rs
Normal file
@ -0,0 +1,33 @@
|
||||
//! We install signal handlers to handle WebAssembly traps within
|
||||
//! our Rust code. Otherwise we will have errors that stop the Rust process
|
||||
//! such as `process didn't exit successfully: ... (signal: 8, SIGFPE: erroneous arithmetic operation)`
|
||||
//!
|
||||
//! Please read more about this here: https://github.com/CraneStation/wasmtime/issues/15
|
||||
//! This code is inspired by: https://github.com/pepyakin/wasmtime/commit/625a2b6c0815b21996e111da51b9664feb174622
|
||||
use crate::recovery;
|
||||
use nix::libc::{c_void, siginfo_t};
|
||||
use nix::sys::signal::{
|
||||
sigaction, SaFlags, SigAction, SigHandler, SigSet, SIGBUS, SIGFPE, SIGILL, SIGSEGV,
|
||||
};
|
||||
|
||||
pub unsafe fn install_sighandler() {
|
||||
let sa = SigAction::new(
|
||||
SigHandler::SigAction(signal_trap_handler),
|
||||
SaFlags::SA_ONSTACK,
|
||||
SigSet::empty(),
|
||||
);
|
||||
sigaction(SIGFPE, &sa).unwrap();
|
||||
sigaction(SIGILL, &sa).unwrap();
|
||||
sigaction(SIGSEGV, &sa).unwrap();
|
||||
sigaction(SIGBUS, &sa).unwrap();
|
||||
}
|
||||
|
||||
extern "C" fn signal_trap_handler(
|
||||
signum: ::nix::libc::c_int,
|
||||
siginfo: *mut siginfo_t,
|
||||
_ucontext: *mut c_void,
|
||||
) {
|
||||
unsafe {
|
||||
recovery::do_unwind(signum, siginfo);
|
||||
}
|
||||
}
|
36
lib/runtime/src/table.rs
Normal file
36
lib/runtime/src/table.rs
Normal file
@ -0,0 +1,36 @@
|
||||
use super::vm;
|
||||
use crate::types::{ElementType, Table};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum TableElements {
|
||||
/// This is intended to be a caller-checked Anyfunc.
|
||||
Anyfunc(Box<[vm::Anyfunc]>),
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct TableBacking {
|
||||
pub elements: TableElements,
|
||||
pub max: Option<u32>,
|
||||
}
|
||||
|
||||
impl TableBacking {
|
||||
pub fn new(table: &Table) -> Self {
|
||||
match table.ty {
|
||||
ElementType::Anyfunc => Self {
|
||||
elements: TableElements::Anyfunc(
|
||||
vec![vm::Anyfunc::null(); table.min as usize].into_boxed_slice(),
|
||||
),
|
||||
max: table.max,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn into_vm_table(&mut self) -> vm::LocalTable {
|
||||
match self.elements {
|
||||
TableElements::Anyfunc(ref mut funcs) => vm::LocalTable {
|
||||
base: funcs.as_mut_ptr() as *mut u8,
|
||||
current_elements: funcs.len(),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
322
lib/runtime/src/types.rs
Normal file
322
lib/runtime/src/types.rs
Normal file
@ -0,0 +1,322 @@
|
||||
use std::marker::PhantomData;
|
||||
use std::{
|
||||
iter,
|
||||
ops::{Index, IndexMut},
|
||||
slice,
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
pub enum Type {
|
||||
/// The `i32` type.
|
||||
I32,
|
||||
/// The `i64` type.
|
||||
I64,
|
||||
/// The `f32` type.
|
||||
F32,
|
||||
/// The `f64` type.
|
||||
F64,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum Value {
|
||||
/// The `i32` type.
|
||||
I32(i32),
|
||||
/// The `i64` type.
|
||||
I64(i64),
|
||||
/// The `f32` type.
|
||||
F32(u32),
|
||||
/// The `f64` type.
|
||||
F64(u64),
|
||||
}
|
||||
|
||||
impl Value {
|
||||
pub fn ty(&self) -> Type {
|
||||
match self {
|
||||
Value::I32(_) => Type::I32,
|
||||
Value::I64(_) => Type::I64,
|
||||
Value::F32(_) => Type::F32,
|
||||
Value::F64(_) => Type::F64,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<i32> for Value {
|
||||
fn from(i: i32) -> Self {
|
||||
Value::I32(i)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<i64> for Value {
|
||||
fn from(i: i64) -> Self {
|
||||
Value::I64(i)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<f32> for Value {
|
||||
fn from(f: f32) -> Self {
|
||||
Value::F32(f as _)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<f64> for Value {
|
||||
fn from(f: f64) -> Self {
|
||||
Value::F64(f as _)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum ElementType {
|
||||
/// Any wasm function.
|
||||
Anyfunc,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct Table {
|
||||
/// Type of data stored in this table.
|
||||
pub ty: ElementType,
|
||||
/// The minimum number of elements that must be stored in this table.
|
||||
pub min: u32,
|
||||
/// The maximum number of elements in this table.
|
||||
pub max: Option<u32>,
|
||||
}
|
||||
|
||||
/// A global value initializer.
|
||||
/// Overtime, this will be able to represent more and more
|
||||
/// complex expressions.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum Initializer {
|
||||
/// Corresponds to a `const.*` instruction.
|
||||
Const(Value),
|
||||
/// Corresponds to a `get_global` instruction.
|
||||
GetGlobal(GlobalIndex),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub struct GlobalDesc {
|
||||
pub mutable: bool,
|
||||
pub ty: Type,
|
||||
}
|
||||
|
||||
/// A wasm global.
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct Global {
|
||||
pub desc: GlobalDesc,
|
||||
pub init: Initializer,
|
||||
}
|
||||
|
||||
/// A wasm memory.
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct Memory {
|
||||
/// The minimum number of allowed pages.
|
||||
pub min: u32,
|
||||
/// The maximum number of allowed pages.
|
||||
pub max: Option<u32>,
|
||||
/// This memory can be shared between wasm threads.
|
||||
pub shared: bool,
|
||||
}
|
||||
|
||||
impl Memory {
|
||||
pub fn is_static_heap(&self) -> bool {
|
||||
self.max.is_some()
|
||||
}
|
||||
}
|
||||
|
||||
/// A wasm func.
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||
pub struct FuncSig {
|
||||
pub params: Vec<Type>,
|
||||
pub returns: Vec<Type>,
|
||||
}
|
||||
|
||||
impl FuncSig {
|
||||
pub fn check_sig(&self, params: &[Value]) -> bool {
|
||||
self.params.len() == params.len()
|
||||
&& self
|
||||
.params
|
||||
.iter()
|
||||
.zip(params.iter().map(|val| val.ty()))
|
||||
.all(|(t0, ref t1)| t0 == t1)
|
||||
}
|
||||
}
|
||||
|
||||
pub trait MapIndex {
|
||||
fn new(index: usize) -> Self;
|
||||
fn index(&self) -> usize;
|
||||
}
|
||||
|
||||
/// Dense item map
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Map<I, T>
|
||||
where
|
||||
I: MapIndex,
|
||||
{
|
||||
elems: Vec<T>,
|
||||
_marker: PhantomData<I>,
|
||||
}
|
||||
|
||||
impl<I, T> Map<I, T>
|
||||
where
|
||||
I: MapIndex,
|
||||
{
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
elems: Vec::new(),
|
||||
_marker: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn with_capacity(capacity: usize) -> Self {
|
||||
Self {
|
||||
elems: Vec::with_capacity(capacity),
|
||||
_marker: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get(&self, index: I) -> Option<&T> {
|
||||
self.elems.get(index.index())
|
||||
}
|
||||
|
||||
pub fn len(&self) -> usize {
|
||||
self.elems.len()
|
||||
}
|
||||
|
||||
pub fn push(&mut self, value: T) -> I {
|
||||
let len = self.len();
|
||||
self.elems.push(value);
|
||||
I::new(len)
|
||||
}
|
||||
|
||||
pub fn as_ptr(&self) -> *const T {
|
||||
self.elems.as_ptr()
|
||||
}
|
||||
|
||||
pub fn reserve_exact(&mut self, size: usize) {
|
||||
self.elems.reserve_exact(size);
|
||||
}
|
||||
|
||||
pub fn iter(&self) -> Iter<T, I> {
|
||||
Iter::new(self.elems.iter())
|
||||
}
|
||||
}
|
||||
|
||||
impl<I, T> Index<I> for Map<I, T>
|
||||
where
|
||||
I: MapIndex,
|
||||
{
|
||||
type Output = T;
|
||||
fn index(&self, index: I) -> &T {
|
||||
&self.elems[index.index()]
|
||||
}
|
||||
}
|
||||
|
||||
impl<I, T> IndexMut<I> for Map<I, T>
|
||||
where
|
||||
I: MapIndex,
|
||||
{
|
||||
fn index_mut(&mut self, index: I) -> &mut T {
|
||||
&mut self.elems[index.index()]
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, I, T> IntoIterator for &'a Map<I, T>
|
||||
where
|
||||
I: MapIndex,
|
||||
{
|
||||
type Item = (I, &'a T);
|
||||
type IntoIter = Iter<'a, T, I>;
|
||||
|
||||
fn into_iter(self) -> Self::IntoIter {
|
||||
Iter::new(self.elems.iter())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, I, T> IntoIterator for &'a mut Map<I, T>
|
||||
where
|
||||
I: MapIndex,
|
||||
{
|
||||
type Item = (I, &'a mut T);
|
||||
type IntoIter = IterMut<'a, T, I>;
|
||||
|
||||
fn into_iter(self) -> Self::IntoIter {
|
||||
IterMut::new(self.elems.iter_mut())
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Iter<'a, T: 'a, I: MapIndex> {
|
||||
enumerated: iter::Enumerate<slice::Iter<'a, T>>,
|
||||
_marker: PhantomData<I>,
|
||||
}
|
||||
|
||||
impl<'a, T: 'a, I: MapIndex> Iter<'a, T, I> {
|
||||
fn new(iter: slice::Iter<'a, T>) -> Self {
|
||||
Self {
|
||||
enumerated: iter.enumerate(),
|
||||
_marker: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T: 'a, I: MapIndex> Iterator for Iter<'a, T, I> {
|
||||
type Item = (I, &'a T);
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
self.enumerated.next().map(|(i, v)| (I::new(i), v))
|
||||
}
|
||||
}
|
||||
|
||||
pub struct IterMut<'a, T: 'a, I: MapIndex> {
|
||||
enumerated: iter::Enumerate<slice::IterMut<'a, T>>,
|
||||
_marker: PhantomData<I>,
|
||||
}
|
||||
|
||||
impl<'a, T: 'a, I: MapIndex> IterMut<'a, T, I> {
|
||||
fn new(iter: slice::IterMut<'a, T>) -> Self {
|
||||
Self {
|
||||
enumerated: iter.enumerate(),
|
||||
_marker: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T: 'a, I: MapIndex> Iterator for IterMut<'a, T, I> {
|
||||
type Item = (I, &'a mut T);
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
self.enumerated.next().map(|(i, v)| (I::new(i), v))
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! define_map_index {
|
||||
($ty:ident) => {
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||||
pub struct $ty (u32);
|
||||
impl MapIndex for $ty {
|
||||
fn new(index: usize) -> Self {
|
||||
$ty (index as _)
|
||||
}
|
||||
|
||||
fn index(&self) -> usize {
|
||||
self.0 as usize
|
||||
}
|
||||
}
|
||||
};
|
||||
($($ty:ident,)*) => {
|
||||
$(
|
||||
define_map_index!($ty);
|
||||
)*
|
||||
};
|
||||
}
|
||||
|
||||
define_map_index![FuncIndex, MemoryIndex, TableIndex, SigIndex,];
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||||
pub struct GlobalIndex(u32);
|
||||
impl MapIndex for GlobalIndex {
|
||||
fn new(index: usize) -> Self {
|
||||
GlobalIndex(index as _)
|
||||
}
|
||||
|
||||
fn index(&self) -> usize {
|
||||
self.0 as usize
|
||||
}
|
||||
}
|
383
lib/runtime/src/vm.rs
Normal file
383
lib/runtime/src/vm.rs
Normal file
@ -0,0 +1,383 @@
|
||||
use crate::backing::{ImportBacking, LocalBacking};
|
||||
use std::{mem, ptr};
|
||||
|
||||
#[derive(Debug)]
|
||||
#[repr(C)]
|
||||
pub struct Ctx {
|
||||
/// A pointer to an array of locally-defined memories, indexed by `MemoryIndex`.
|
||||
pub memories: *mut LocalMemory,
|
||||
|
||||
/// A pointer to an array of locally-defined tables, indexed by `TableIndex`.
|
||||
pub tables: *mut LocalTable,
|
||||
|
||||
/// A pointer to an array of locally-defined globals, indexed by `GlobalIndex`.
|
||||
pub globals: *mut LocalGlobal,
|
||||
|
||||
/// A pointer to an array of imported memories, indexed by `MemoryIndex,
|
||||
pub imported_memories: *mut ImportedMemory,
|
||||
|
||||
/// A pointer to an array of imported tables, indexed by `TableIndex`.
|
||||
pub imported_tables: *mut ImportedTable,
|
||||
|
||||
/// A pointer to an array of imported globals, indexed by `GlobalIndex`.
|
||||
pub imported_globals: *mut ImportedGlobal,
|
||||
|
||||
/// A pointer to an array of imported functions, indexed by `FuncIndex`.
|
||||
pub imported_funcs: *mut ImportedFunc,
|
||||
|
||||
/// Signature identifiers for signature-checked indirect calls.
|
||||
pub signatures: *const SigId,
|
||||
|
||||
/// The parent instance.
|
||||
pub local_backing: *mut LocalBacking,
|
||||
}
|
||||
|
||||
impl Ctx {
|
||||
pub fn new(
|
||||
local_backing: &mut LocalBacking,
|
||||
import_backing: &mut ImportBacking,
|
||||
) -> Self {
|
||||
Self {
|
||||
memories: local_backing.vm_memories.as_mut_ptr(),
|
||||
tables: local_backing.vm_tables.as_mut_ptr(),
|
||||
globals: local_backing.vm_globals.as_mut_ptr(),
|
||||
|
||||
imported_memories: import_backing.memories.as_mut_ptr(),
|
||||
imported_tables: import_backing.tables.as_mut_ptr(),
|
||||
imported_globals: import_backing.globals.as_mut_ptr(),
|
||||
imported_funcs: import_backing.functions.as_mut_ptr(),
|
||||
|
||||
signatures: local_backing.vm_signatures.as_mut_ptr(),
|
||||
local_backing,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn offset_memories() -> u8 {
|
||||
0 * (mem::size_of::<usize>() as u8)
|
||||
}
|
||||
|
||||
pub fn offset_tables() -> u8 {
|
||||
1 * (mem::size_of::<usize>() as u8)
|
||||
}
|
||||
|
||||
pub fn offset_globals() -> u8 {
|
||||
2 * (mem::size_of::<usize>() as u8)
|
||||
}
|
||||
|
||||
pub fn offset_imported_memories() -> u8 {
|
||||
3 * (mem::size_of::<usize>() as u8)
|
||||
}
|
||||
|
||||
pub fn offset_imported_tables() -> u8 {
|
||||
4 * (mem::size_of::<usize>() as u8)
|
||||
}
|
||||
|
||||
pub fn offset_imported_globals() -> u8 {
|
||||
5 * (mem::size_of::<usize>() as u8)
|
||||
}
|
||||
|
||||
pub fn offset_imported_funcs() -> u8 {
|
||||
6 * (mem::size_of::<usize>() as u8)
|
||||
}
|
||||
|
||||
pub fn offset_signatures() -> u8 {
|
||||
7 * (mem::size_of::<usize>() as u8)
|
||||
}
|
||||
}
|
||||
|
||||
/// Used to provide type safety (ish) for passing around function pointers.
|
||||
/// The typesystem ensures this cannot be dereferenced since an
|
||||
/// empty enum cannot actually exist.
|
||||
pub enum Func {}
|
||||
|
||||
/// An imported function, which contains the vmctx that owns this function.
|
||||
#[derive(Debug, Clone)]
|
||||
#[repr(C)]
|
||||
pub struct ImportedFunc {
|
||||
pub func: *const Func,
|
||||
}
|
||||
|
||||
impl ImportedFunc {
|
||||
pub fn offset_func() -> u8 {
|
||||
0 * (mem::size_of::<usize>() as u8)
|
||||
}
|
||||
}
|
||||
|
||||
/// Definition of a table used by the VM. (obviously)
|
||||
#[derive(Debug, Clone)]
|
||||
#[repr(C)]
|
||||
pub struct LocalTable {
|
||||
/// pointer to the elements in the table.
|
||||
pub base: *mut u8,
|
||||
/// Number of elements in the table (NOT necessarily the size of the table in bytes!).
|
||||
pub current_elements: usize,
|
||||
}
|
||||
|
||||
impl LocalTable {
|
||||
pub fn offset_base() -> u8 {
|
||||
0 * (mem::size_of::<usize>() as u8)
|
||||
}
|
||||
|
||||
pub fn offset_current_elements() -> u8 {
|
||||
1 * (mem::size_of::<usize>() as u8)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
#[repr(C)]
|
||||
pub struct ImportedTable {
|
||||
/// A pointer to the table definition.
|
||||
pub table: *mut LocalTable,
|
||||
/// A pointer to the vmcontext that owns this table definition.
|
||||
pub vmctx: *mut Ctx,
|
||||
}
|
||||
|
||||
impl ImportedTable {
|
||||
pub fn offset_table() -> u8 {
|
||||
0 * (mem::size_of::<usize>() as u8)
|
||||
}
|
||||
|
||||
pub fn offset_vmctx() -> u8 {
|
||||
1 * (mem::size_of::<usize>() as u8)
|
||||
}
|
||||
}
|
||||
|
||||
/// Definition of a memory used by the VM.
|
||||
#[derive(Debug, Clone)]
|
||||
#[repr(C)]
|
||||
pub struct LocalMemory {
|
||||
/// Pointer to the bottom of this linear memory.
|
||||
pub base: *mut u8,
|
||||
/// Current size of this linear memory in bytes.
|
||||
pub size: usize,
|
||||
}
|
||||
|
||||
impl LocalMemory {
|
||||
pub fn offset_base() -> u8 {
|
||||
0 * (mem::size_of::<usize>() as u8)
|
||||
}
|
||||
|
||||
pub fn offset_size() -> u8 {
|
||||
1 * (mem::size_of::<usize>() as u8)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
#[repr(C)]
|
||||
pub struct ImportedMemory {
|
||||
/// A pointer to the memory definition.
|
||||
pub memory: *mut LocalMemory,
|
||||
}
|
||||
|
||||
impl ImportedMemory {
|
||||
pub fn offset_memory() -> u8 {
|
||||
0 * (mem::size_of::<usize>() as u8)
|
||||
}
|
||||
}
|
||||
|
||||
/// Definition of a global used by the VM.
|
||||
#[derive(Debug, Clone)]
|
||||
#[repr(C)]
|
||||
pub struct LocalGlobal {
|
||||
pub data: u64,
|
||||
}
|
||||
|
||||
impl LocalGlobal {
|
||||
pub fn offset_data() -> u8 {
|
||||
0 * (mem::size_of::<usize>() as u8)
|
||||
}
|
||||
|
||||
pub fn null() -> Self {
|
||||
Self { data: 0 }
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
#[repr(C)]
|
||||
pub struct ImportedGlobal {
|
||||
pub global: LocalGlobal,
|
||||
}
|
||||
|
||||
impl ImportedGlobal {
|
||||
pub fn offset_data() -> u8 {
|
||||
0 * (mem::size_of::<usize>() as u8)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
#[repr(transparent)]
|
||||
pub struct SigId(pub u32);
|
||||
|
||||
/// Caller-checked anyfunc
|
||||
#[derive(Debug, Clone)]
|
||||
#[repr(C)]
|
||||
pub struct Anyfunc {
|
||||
pub func_data: ImportedFunc,
|
||||
pub sig_id: SigId,
|
||||
}
|
||||
|
||||
impl Anyfunc {
|
||||
pub fn null() -> Self {
|
||||
Self {
|
||||
func_data: ImportedFunc { func: ptr::null() },
|
||||
sig_id: SigId(u32::max_value()),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn offset_func() -> u8 {
|
||||
0 * (mem::size_of::<usize>() as u8)
|
||||
}
|
||||
|
||||
// pub fn offset_vmctx() -> u8 {
|
||||
// 1 * (mem::size_of::<usize>() as u8)
|
||||
// }
|
||||
|
||||
pub fn offset_sig_id() -> u8 {
|
||||
1 * (mem::size_of::<usize>() as u8)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod vm_offset_tests {
|
||||
use super::{
|
||||
Anyfunc, Ctx, ImportedFunc, ImportedGlobal, ImportedMemory, ImportedTable, LocalGlobal,
|
||||
LocalMemory, LocalTable,
|
||||
};
|
||||
|
||||
#[test]
|
||||
fn vmctx() {
|
||||
assert_eq!(
|
||||
Ctx::offset_memories() as usize,
|
||||
offset_of!(Ctx => memories).get_byte_offset(),
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
Ctx::offset_tables() as usize,
|
||||
offset_of!(Ctx => tables).get_byte_offset(),
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
Ctx::offset_globals() as usize,
|
||||
offset_of!(Ctx => globals).get_byte_offset(),
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
Ctx::offset_imported_memories() as usize,
|
||||
offset_of!(Ctx => imported_memories).get_byte_offset(),
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
Ctx::offset_imported_tables() as usize,
|
||||
offset_of!(Ctx => imported_tables).get_byte_offset(),
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
Ctx::offset_imported_globals() as usize,
|
||||
offset_of!(Ctx => imported_globals).get_byte_offset(),
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
Ctx::offset_imported_funcs() as usize,
|
||||
offset_of!(Ctx => imported_funcs).get_byte_offset(),
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
Ctx::offset_signatures() as usize,
|
||||
offset_of!(Ctx => signatures).get_byte_offset(),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn imported_func() {
|
||||
assert_eq!(
|
||||
ImportedFunc::offset_func() as usize,
|
||||
offset_of!(ImportedFunc => func).get_byte_offset(),
|
||||
);
|
||||
|
||||
// assert_eq!(
|
||||
// ImportedFunc::offset_vmctx() as usize,
|
||||
// offset_of!(ImportedFunc => vmctx).get_byte_offset(),
|
||||
// );
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn local_table() {
|
||||
assert_eq!(
|
||||
LocalTable::offset_base() as usize,
|
||||
offset_of!(LocalTable => base).get_byte_offset(),
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
LocalTable::offset_current_elements() as usize,
|
||||
offset_of!(LocalTable => current_elements).get_byte_offset(),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn imported_table() {
|
||||
assert_eq!(
|
||||
ImportedTable::offset_table() as usize,
|
||||
offset_of!(ImportedTable => table).get_byte_offset(),
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
ImportedTable::offset_vmctx() as usize,
|
||||
offset_of!(ImportedTable => vmctx).get_byte_offset(),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn local_memory() {
|
||||
assert_eq!(
|
||||
LocalMemory::offset_base() as usize,
|
||||
offset_of!(LocalMemory => base).get_byte_offset(),
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
LocalMemory::offset_size() as usize,
|
||||
offset_of!(LocalMemory => size).get_byte_offset(),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn imported_memory() {
|
||||
assert_eq!(
|
||||
ImportedMemory::offset_memory() as usize,
|
||||
offset_of!(ImportedMemory => memory).get_byte_offset(),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn local_global() {
|
||||
assert_eq!(
|
||||
LocalGlobal::offset_data() as usize,
|
||||
offset_of!(LocalGlobal => data).get_byte_offset(),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn imported_global() {
|
||||
assert_eq!(
|
||||
ImportedGlobal::offset_data() as usize,
|
||||
offset_of!(ImportedGlobal => global: LocalGlobal => data).get_byte_offset(),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cc_anyfunc() {
|
||||
assert_eq!(
|
||||
Anyfunc::offset_func() as usize,
|
||||
offset_of!(Anyfunc => func_data: ImportedFunc => func).get_byte_offset(),
|
||||
);
|
||||
|
||||
// assert_eq!(
|
||||
// Anyfunc::offset_vmctx() as usize,
|
||||
// offset_of!(Anyfunc => func_data: ImportedFunc => vmctx).get_byte_offset(),
|
||||
// );
|
||||
|
||||
assert_eq!(
|
||||
Anyfunc::offset_sig_id() as usize,
|
||||
offset_of!(Anyfunc => sig_id).get_byte_offset(),
|
||||
);
|
||||
}
|
||||
}
|
21
lib/runtime/src/vmcalls.rs
Normal file
21
lib/runtime/src/vmcalls.rs
Normal file
@ -0,0 +1,21 @@
|
||||
use crate::{memory::LinearMemory, vm};
|
||||
|
||||
pub unsafe extern "C" fn memory_grow_static(
|
||||
memory_index: u32,
|
||||
by_pages: u32,
|
||||
ctx: *mut vm::Ctx,
|
||||
) -> i32 {
|
||||
if let Some(old) = (*(*ctx).local_backing).memories[memory_index as usize].grow_static(by_pages)
|
||||
{
|
||||
// Store the new size back into the vmctx.
|
||||
(*(*ctx).memories.add(memory_index as usize)).size =
|
||||
(old as usize + by_pages as usize) * LinearMemory::PAGE_SIZE as usize;
|
||||
old
|
||||
} else {
|
||||
-1
|
||||
}
|
||||
}
|
||||
|
||||
pub unsafe extern "C" fn memory_size(memory_index: u32, ctx: *mut vm::Ctx) -> u32 {
|
||||
(*(*ctx).local_backing).memories[memory_index as usize].current_pages()
|
||||
}
|
Reference in New Issue
Block a user