Browse Source
* Remove the `action` and `context` modules from `wasmtime_jit` These modules are now no longer necessary with the `wasmtime` crate fleshed out, and they're entirely subsumed by the `wasmtime` API as well. * Remove some more modulespull/930/head
Alex Crichton
5 years ago
committed by
GitHub
8 changed files with 55 additions and 642 deletions
@ -1,295 +0,0 @@ |
|||
//! Support for performing actions with a wasm module from the outside.
|
|||
|
|||
use crate::compiler::Compiler; |
|||
use crate::instantiate::SetupError; |
|||
use std::cmp::max; |
|||
use std::{fmt, mem, ptr, slice}; |
|||
use thiserror::Error; |
|||
use wasmtime_environ::ir; |
|||
use wasmtime_runtime::{wasmtime_call_trampoline, Export, InstanceHandle, Trap, VMInvokeArgument}; |
|||
|
|||
/// A runtime value.
|
|||
#[derive(Copy, Clone, Debug, Eq, PartialEq)] |
|||
pub enum RuntimeValue { |
|||
/// A runtime value with type i32.
|
|||
I32(i32), |
|||
/// A runtime value with type i64.
|
|||
I64(i64), |
|||
/// A runtime value with type f32.
|
|||
F32(u32), |
|||
/// A runtime value with type f64.
|
|||
F64(u64), |
|||
/// A runtime value with type v128
|
|||
V128([u8; 16]), |
|||
} |
|||
|
|||
impl RuntimeValue { |
|||
/// Return the type of this `RuntimeValue`.
|
|||
pub fn value_type(self) -> ir::Type { |
|||
match self { |
|||
Self::I32(_) => ir::types::I32, |
|||
Self::I64(_) => ir::types::I64, |
|||
Self::F32(_) => ir::types::F32, |
|||
Self::F64(_) => ir::types::F64, |
|||
Self::V128(_) => ir::types::I8X16, |
|||
} |
|||
} |
|||
|
|||
/// Assuming this `RuntimeValue` holds an `i32`, return that value.
|
|||
pub fn unwrap_i32(self) -> i32 { |
|||
match self { |
|||
Self::I32(x) => x, |
|||
_ => panic!("unwrapping value of type {} as i32", self.value_type()), |
|||
} |
|||
} |
|||
|
|||
/// Assuming this `RuntimeValue` holds an `i64`, return that value.
|
|||
pub fn unwrap_i64(self) -> i64 { |
|||
match self { |
|||
Self::I64(x) => x, |
|||
_ => panic!("unwrapping value of type {} as i64", self.value_type()), |
|||
} |
|||
} |
|||
|
|||
/// Assuming this `RuntimeValue` holds an `f32`, return that value.
|
|||
pub fn unwrap_f32(self) -> f32 { |
|||
f32::from_bits(self.unwrap_f32_bits()) |
|||
} |
|||
|
|||
/// Assuming this `RuntimeValue` holds an `f32`, return the bits of that value as a `u32`.
|
|||
pub fn unwrap_f32_bits(self) -> u32 { |
|||
match self { |
|||
Self::F32(x) => x, |
|||
_ => panic!("unwrapping value of type {} as f32", self.value_type()), |
|||
} |
|||
} |
|||
|
|||
/// Assuming this `RuntimeValue` holds an `f64`, return that value.
|
|||
pub fn unwrap_f64(self) -> f64 { |
|||
f64::from_bits(self.unwrap_f64_bits()) |
|||
} |
|||
|
|||
/// Assuming this `RuntimeValue` holds an `f64`, return the bits of that value as a `u64`.
|
|||
pub fn unwrap_f64_bits(self) -> u64 { |
|||
match self { |
|||
Self::F64(x) => x, |
|||
_ => panic!("unwrapping value of type {} as f64", self.value_type()), |
|||
} |
|||
} |
|||
} |
|||
|
|||
impl fmt::Display for RuntimeValue { |
|||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
|||
match self { |
|||
Self::I32(x) => write!(f, "{}: i32", x), |
|||
Self::I64(x) => write!(f, "{}: i64", x), |
|||
Self::F32(x) => write!(f, "{}: f32", x), |
|||
Self::F64(x) => write!(f, "{}: f64", x), |
|||
Self::V128(x) => write!(f, "{:?}: v128", x.to_vec()), |
|||
} |
|||
} |
|||
} |
|||
|
|||
/// The result of invoking a wasm function or reading a wasm global.
|
|||
#[derive(Debug)] |
|||
pub enum ActionOutcome { |
|||
/// The action returned normally. Its return values are provided.
|
|||
Returned { |
|||
/// The return values.
|
|||
values: Vec<RuntimeValue>, |
|||
}, |
|||
|
|||
/// A trap occurred while the action was executing.
|
|||
Trapped(Trap), |
|||
} |
|||
|
|||
/// An error detected while invoking a wasm function or reading a wasm global.
|
|||
/// Note that at this level, traps are not reported errors, but are rather
|
|||
/// returned through `ActionOutcome`.
|
|||
#[derive(Error, Debug)] |
|||
pub enum ActionError { |
|||
/// An internal implementation error occurred.
|
|||
#[error("Failed to setup a module")] |
|||
Setup(#[from] SetupError), |
|||
|
|||
/// No field with the specified name was present.
|
|||
#[error("Unknown field: {0}")] |
|||
Field(String), |
|||
|
|||
/// The field was present but was the wrong kind (eg. function, table, global, or memory).
|
|||
#[error("Kind error: {0}")] |
|||
Kind(String), |
|||
|
|||
/// The field was present but was the wrong type (eg. i32, i64, f32, or f64).
|
|||
#[error("Type error: {0}")] |
|||
Type(String), |
|||
} |
|||
|
|||
/// Invoke a function through an `InstanceHandle` identified by an export name.
|
|||
pub fn invoke( |
|||
compiler: &mut Compiler, |
|||
instance: &mut InstanceHandle, |
|||
function_name: &str, |
|||
args: &[RuntimeValue], |
|||
) -> Result<ActionOutcome, ActionError> { |
|||
let (address, signature, callee_vmctx) = match instance.lookup(function_name) { |
|||
Some(Export::Function { |
|||
address, |
|||
signature, |
|||
vmctx, |
|||
}) => (address, signature, vmctx), |
|||
Some(_) => { |
|||
return Err(ActionError::Kind(format!( |
|||
"exported item \"{}\" is not a function", |
|||
function_name |
|||
))); |
|||
} |
|||
None => { |
|||
return Err(ActionError::Field(format!( |
|||
"no export named \"{}\"", |
|||
function_name |
|||
))); |
|||
} |
|||
}; |
|||
|
|||
for (index, value) in args.iter().enumerate() { |
|||
// Add one to account for the leading vmctx argument.
|
|||
assert_eq!(value.value_type(), signature.params[index + 1].value_type); |
|||
} |
|||
|
|||
// TODO: Support values larger than v128. And pack the values into memory
|
|||
// instead of just using fixed-sized slots.
|
|||
// Subtract one because we don't pass the vmctx argument in `values_vec`.
|
|||
let value_size = mem::size_of::<VMInvokeArgument>(); |
|||
let mut values_vec: Vec<VMInvokeArgument> = |
|||
vec![VMInvokeArgument::new(); max(signature.params.len() - 1, signature.returns.len())]; |
|||
|
|||
// Store the argument values into `values_vec`.
|
|||
for (index, arg) in args.iter().enumerate() { |
|||
unsafe { |
|||
let ptr = values_vec.as_mut_ptr().add(index); |
|||
|
|||
match arg { |
|||
RuntimeValue::I32(x) => ptr::write(ptr as *mut i32, *x), |
|||
RuntimeValue::I64(x) => ptr::write(ptr as *mut i64, *x), |
|||
RuntimeValue::F32(x) => ptr::write(ptr as *mut u32, *x), |
|||
RuntimeValue::F64(x) => ptr::write(ptr as *mut u64, *x), |
|||
RuntimeValue::V128(x) => ptr::write(ptr as *mut [u8; 16], *x), |
|||
} |
|||
} |
|||
} |
|||
|
|||
// Get the trampoline to call for this function.
|
|||
let exec_code_buf = compiler |
|||
.get_trampoline(address, &signature, value_size) |
|||
.map_err(ActionError::Setup)?; |
|||
|
|||
// Make all JIT code produced thus far executable.
|
|||
compiler.publish_compiled_code(); |
|||
|
|||
// Call the trampoline. Pass a null `caller_vmctx` argument as `invoke` is
|
|||
// all about calling from the outside world rather than from an instance.
|
|||
if let Err(trap) = unsafe { |
|||
wasmtime_call_trampoline( |
|||
callee_vmctx, |
|||
ptr::null_mut(), |
|||
exec_code_buf, |
|||
values_vec.as_mut_ptr() as *mut u8, |
|||
) |
|||
} { |
|||
return Ok(ActionOutcome::Trapped(trap)); |
|||
} |
|||
|
|||
// Load the return values out of `values_vec`.
|
|||
let values = signature |
|||
.returns |
|||
.iter() |
|||
.enumerate() |
|||
.map(|(index, abi_param)| unsafe { |
|||
let ptr = values_vec.as_ptr().add(index); |
|||
|
|||
match abi_param.value_type { |
|||
ir::types::I32 => RuntimeValue::I32(ptr::read(ptr as *const i32)), |
|||
ir::types::I64 => RuntimeValue::I64(ptr::read(ptr as *const i64)), |
|||
ir::types::F32 => RuntimeValue::F32(ptr::read(ptr as *const u32)), |
|||
ir::types::F64 => RuntimeValue::F64(ptr::read(ptr as *const u64)), |
|||
ir::types::I8X16 => RuntimeValue::V128(ptr::read(ptr as *const [u8; 16])), |
|||
other => panic!("unsupported value type {:?}", other), |
|||
} |
|||
}) |
|||
.collect(); |
|||
|
|||
Ok(ActionOutcome::Returned { values }) |
|||
} |
|||
|
|||
/// Returns a slice of the contents of allocated linear memory.
|
|||
pub fn inspect_memory<'instance>( |
|||
instance: &'instance InstanceHandle, |
|||
memory_name: &str, |
|||
start: usize, |
|||
len: usize, |
|||
) -> Result<&'instance [u8], ActionError> { |
|||
let definition = match instance.lookup(memory_name) { |
|||
Some(Export::Memory { |
|||
definition, |
|||
memory: _memory, |
|||
vmctx: _vmctx, |
|||
}) => definition, |
|||
Some(_) => { |
|||
return Err(ActionError::Kind(format!( |
|||
"exported item \"{}\" is not a linear memory", |
|||
memory_name |
|||
))); |
|||
} |
|||
None => { |
|||
return Err(ActionError::Field(format!( |
|||
"no export named \"{}\"", |
|||
memory_name |
|||
))); |
|||
} |
|||
}; |
|||
|
|||
Ok(unsafe { |
|||
let memory_def = &*definition; |
|||
&slice::from_raw_parts(memory_def.base, memory_def.current_length)[start..start + len] |
|||
}) |
|||
} |
|||
|
|||
/// Read a global in the given instance identified by an export name.
|
|||
pub fn get(instance: &InstanceHandle, global_name: &str) -> Result<RuntimeValue, ActionError> { |
|||
let (definition, global) = match instance.lookup(global_name) { |
|||
Some(Export::Global { |
|||
definition, |
|||
vmctx: _, |
|||
global, |
|||
}) => (definition, global), |
|||
Some(_) => { |
|||
return Err(ActionError::Kind(format!( |
|||
"exported item \"{}\" is not a global variable", |
|||
global_name |
|||
))); |
|||
} |
|||
None => { |
|||
return Err(ActionError::Field(format!( |
|||
"no export named \"{}\"", |
|||
global_name |
|||
))); |
|||
} |
|||
}; |
|||
|
|||
unsafe { |
|||
let global_def = &*definition; |
|||
Ok(match global.ty { |
|||
ir::types::I32 => RuntimeValue::I32(*global_def.as_i32()), |
|||
ir::types::I64 => RuntimeValue::I64(*global_def.as_i64()), |
|||
ir::types::F32 => RuntimeValue::F32(*global_def.as_f32_bits()), |
|||
ir::types::F64 => RuntimeValue::F64(*global_def.as_f64_bits()), |
|||
other => { |
|||
return Err(ActionError::Type(format!( |
|||
"global with type {} not supported", |
|||
other |
|||
))); |
|||
} |
|||
}) |
|||
} |
|||
} |
@ -1,222 +0,0 @@ |
|||
use crate::action::{get, inspect_memory, invoke}; |
|||
use crate::{ |
|||
instantiate, ActionError, ActionOutcome, CompiledModule, Compiler, InstanceHandle, Namespace, |
|||
RuntimeValue, SetupError, |
|||
}; |
|||
use thiserror::Error; |
|||
use wasmparser::{validate, OperatorValidatorConfig, ValidatingParserConfig}; |
|||
|
|||
/// Indicates an unknown instance was specified.
|
|||
#[derive(Error, Debug)] |
|||
#[error("no instance {instance_name} present")] |
|||
pub struct UnknownInstance { |
|||
instance_name: String, |
|||
} |
|||
|
|||
/// Error message used by `WastContext`.
|
|||
#[derive(Error, Debug)] |
|||
pub enum ContextError { |
|||
/// An unknown instance name was used.
|
|||
#[error("An error occured due to an unknown instance being specified")] |
|||
Instance(#[from] UnknownInstance), |
|||
/// An error occured while performing an action.
|
|||
#[error("An error occurred while performing an action")] |
|||
Action(#[from] ActionError), |
|||
} |
|||
|
|||
/// The collection of features configurable during compilation
|
|||
#[derive(Clone, Default, Debug)] |
|||
pub struct Features { |
|||
/// marks whether the proposed thread feature is enabled or disabled
|
|||
pub threads: bool, |
|||
/// marks whether the proposed reference type feature is enabled or disabled
|
|||
pub reference_types: bool, |
|||
/// marks whether the proposed SIMD feature is enabled or disabled
|
|||
pub simd: bool, |
|||
/// marks whether the proposed bulk memory feature is enabled or disabled
|
|||
pub bulk_memory: bool, |
|||
/// marks whether the proposed multi-value feature is enabled or disabled
|
|||
pub multi_value: bool, |
|||
} |
|||
|
|||
impl Into<ValidatingParserConfig> for Features { |
|||
fn into(self) -> ValidatingParserConfig { |
|||
ValidatingParserConfig { |
|||
operator_config: OperatorValidatorConfig { |
|||
enable_threads: self.threads, |
|||
enable_reference_types: self.reference_types, |
|||
enable_bulk_memory: self.bulk_memory, |
|||
enable_simd: self.simd, |
|||
enable_multi_value: self.multi_value, |
|||
}, |
|||
} |
|||
} |
|||
} |
|||
|
|||
/// A convenient context for compiling and executing WebAssembly instances.
|
|||
pub struct Context { |
|||
namespace: Namespace, |
|||
compiler: Box<Compiler>, |
|||
debug_info: bool, |
|||
features: Features, |
|||
} |
|||
|
|||
impl Context { |
|||
/// Construct a new instance of `Context`.
|
|||
pub fn new(compiler: Box<Compiler>) -> Self { |
|||
Self { |
|||
namespace: Namespace::new(), |
|||
compiler, |
|||
debug_info: false, |
|||
features: Default::default(), |
|||
} |
|||
} |
|||
|
|||
/// Get debug_info settings.
|
|||
pub fn debug_info(&self) -> bool { |
|||
self.debug_info |
|||
} |
|||
|
|||
/// Set debug_info settings.
|
|||
pub fn set_debug_info(&mut self, value: bool) { |
|||
self.debug_info = value; |
|||
} |
|||
|
|||
/// Retrieve the context features
|
|||
pub fn features(&self) -> &Features { |
|||
&self.features |
|||
} |
|||
|
|||
/// Construct a new instance with the given features from the current `Context`
|
|||
pub fn with_features(self, features: Features) -> Self { |
|||
Self { features, ..self } |
|||
} |
|||
|
|||
fn validate(&mut self, data: &[u8]) -> Result<(), String> { |
|||
// TODO: Fix Cranelift to be able to perform validation itself, rather
|
|||
// than calling into wasmparser ourselves here.
|
|||
validate(data, Some(self.features.clone().into())) |
|||
.map_err(|e| format!("module did not validate: {}", e.to_string())) |
|||
} |
|||
|
|||
unsafe fn instantiate(&mut self, data: &[u8]) -> Result<InstanceHandle, SetupError> { |
|||
self.validate(&data).map_err(SetupError::Validate)?; |
|||
let debug_info = self.debug_info(); |
|||
|
|||
instantiate(&mut *self.compiler, &data, &mut self.namespace, debug_info) |
|||
} |
|||
|
|||
/// Return the instance associated with the given name.
|
|||
pub fn get_instance( |
|||
&mut self, |
|||
instance_name: &str, |
|||
) -> Result<&mut InstanceHandle, UnknownInstance> { |
|||
self.namespace |
|||
.get_instance(instance_name) |
|||
.ok_or_else(|| UnknownInstance { |
|||
instance_name: instance_name.to_string(), |
|||
}) |
|||
} |
|||
|
|||
/// Instantiate a module instance and register the instance.
|
|||
///
|
|||
/// # Unsafety
|
|||
///
|
|||
/// See `InstanceHandle::new`
|
|||
pub unsafe fn instantiate_module( |
|||
&mut self, |
|||
instance_name: Option<String>, |
|||
data: &[u8], |
|||
) -> Result<InstanceHandle, ActionError> { |
|||
let instance = self.instantiate(data).map_err(ActionError::Setup)?; |
|||
self.optionally_name_instance(instance_name, instance.clone()); |
|||
Ok(instance) |
|||
} |
|||
|
|||
/// Compile a module.
|
|||
pub fn compile_module(&mut self, data: &[u8]) -> Result<CompiledModule, SetupError> { |
|||
self.validate(&data).map_err(SetupError::Validate)?; |
|||
let debug_info = self.debug_info(); |
|||
|
|||
CompiledModule::new(&mut *self.compiler, data, debug_info) |
|||
} |
|||
|
|||
/// If `name` isn't None, register it for the given instance.
|
|||
pub fn optionally_name_instance(&mut self, name: Option<String>, instance: InstanceHandle) { |
|||
if let Some(name) = name { |
|||
self.namespace.name_instance(name, instance); |
|||
} |
|||
} |
|||
|
|||
/// Register a name for the given instance.
|
|||
pub fn name_instance(&mut self, name: String, instance: InstanceHandle) { |
|||
self.namespace.name_instance(name, instance); |
|||
} |
|||
|
|||
/// Register an additional name for an existing registered instance.
|
|||
pub fn alias(&mut self, name: &str, as_name: String) -> Result<(), UnknownInstance> { |
|||
let instance = self.get_instance(&name)?.clone(); |
|||
self.name_instance(as_name, instance); |
|||
Ok(()) |
|||
} |
|||
|
|||
/// Invoke an exported function from a named instance.
|
|||
pub fn invoke_named( |
|||
&mut self, |
|||
instance_name: &str, |
|||
field: &str, |
|||
args: &[RuntimeValue], |
|||
) -> Result<ActionOutcome, ContextError> { |
|||
let mut instance = self |
|||
.get_instance(&instance_name) |
|||
.map_err(ContextError::Instance)? |
|||
.clone(); |
|||
self.invoke(&mut instance, field, args) |
|||
.map_err(ContextError::Action) |
|||
} |
|||
|
|||
/// Invoke an exported function from an instance.
|
|||
pub fn invoke( |
|||
&mut self, |
|||
instance: &mut InstanceHandle, |
|||
field: &str, |
|||
args: &[RuntimeValue], |
|||
) -> Result<ActionOutcome, ActionError> { |
|||
invoke(&mut *self.compiler, instance, field, &args) |
|||
} |
|||
|
|||
/// Get the value of an exported global variable from an instance.
|
|||
pub fn get_named( |
|||
&mut self, |
|||
instance_name: &str, |
|||
field: &str, |
|||
) -> Result<ActionOutcome, ContextError> { |
|||
let instance = self |
|||
.get_instance(&instance_name) |
|||
.map_err(ContextError::Instance)? |
|||
.clone(); |
|||
self.get(&instance, field).map_err(ContextError::Action) |
|||
} |
|||
|
|||
/// Get the value of an exported global variable from an instance.
|
|||
pub fn get( |
|||
&mut self, |
|||
instance: &InstanceHandle, |
|||
field: &str, |
|||
) -> Result<ActionOutcome, ActionError> { |
|||
get(instance, field).map(|value| ActionOutcome::Returned { |
|||
values: vec![value], |
|||
}) |
|||
} |
|||
|
|||
/// Get a slice of memory from an instance.
|
|||
pub fn inspect_memory<'instance>( |
|||
&self, |
|||
instance: &'instance InstanceHandle, |
|||
field_name: &str, |
|||
start: usize, |
|||
len: usize, |
|||
) -> Result<&'instance [u8], ActionError> { |
|||
inspect_memory(instance, field_name, start, len) |
|||
} |
|||
} |
@ -1,46 +0,0 @@ |
|||
//! The core WebAssembly spec does not specify how imports are to be resolved
|
|||
//! to exports. This file provides one possible way to manage multiple instances
|
|||
//! and resolve imports to exports among them.
|
|||
|
|||
use crate::resolver::Resolver; |
|||
use std::collections::HashMap; |
|||
use wasmtime_runtime::{Export, InstanceHandle}; |
|||
|
|||
/// A namespace containing instances keyed by name.
|
|||
///
|
|||
/// Note that `Namespace` implements the `Resolver` trait, so it can resolve
|
|||
/// imports using defined exports.
|
|||
pub struct Namespace { |
|||
/// Mapping from identifiers to indices in `self.instances`.
|
|||
names: HashMap<String, InstanceHandle>, |
|||
} |
|||
|
|||
impl Namespace { |
|||
/// Construct a new `Namespace`.
|
|||
pub fn new() -> Self { |
|||
Self { |
|||
names: HashMap::new(), |
|||
} |
|||
} |
|||
|
|||
/// Install a new `InstanceHandle` in this `Namespace`, optionally with the
|
|||
/// given name.
|
|||
pub fn name_instance(&mut self, name: String, instance: InstanceHandle) { |
|||
self.names.insert(name, instance); |
|||
} |
|||
|
|||
/// Get the instance registered with the given `instance_name`.
|
|||
pub fn get_instance(&mut self, name: &str) -> Option<&mut InstanceHandle> { |
|||
self.names.get_mut(name) |
|||
} |
|||
} |
|||
|
|||
impl Resolver for Namespace { |
|||
fn resolve(&mut self, _idx: u32, name: &str, field: &str) -> Option<Export> { |
|||
if let Some(instance) = self.names.get_mut(name) { |
|||
instance.lookup(field) |
|||
} else { |
|||
None |
|||
} |
|||
} |
|||
} |
Loading…
Reference in new issue