Files
wasmtime/wasmtime-jit/src/action.rs
2019-03-20 11:30:28 -07:00

292 lines
9.4 KiB
Rust

//! Support for performing actions with a wasm module from the outside.
use crate::compiler::Compiler;
use crate::instantiate::SetupError;
use core::cmp::max;
use core::{fmt, mem, ptr, slice};
use cranelift_codegen::ir;
use std::string::String;
use std::vec::Vec;
use wasmtime_runtime::{wasmtime_call_trampoline, Export, InstanceHandle};
/// 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),
}
impl RuntimeValue {
/// Return the type of this `RuntimeValue`.
pub fn value_type(self) -> ir::Type {
match self {
RuntimeValue::I32(_) => ir::types::I32,
RuntimeValue::I64(_) => ir::types::I64,
RuntimeValue::F32(_) => ir::types::F32,
RuntimeValue::F64(_) => ir::types::F64,
}
}
/// Assuming this `RuntimeValue` holds an `i32`, return that value.
pub fn unwrap_i32(self) -> i32 {
match self {
RuntimeValue::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 {
RuntimeValue::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 {
RuntimeValue::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 {
RuntimeValue::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 {
RuntimeValue::I32(x) => write!(f, "{}: i32", x),
RuntimeValue::I64(x) => write!(f, "{}: i64", x),
RuntimeValue::F32(x) => write!(f, "{}: f32", x),
RuntimeValue::F64(x) => write!(f, "{}: f64", x),
}
}
}
/// 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 {
/// The trap message.
message: String,
},
}
/// 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(Fail, Debug)]
pub enum ActionError {
/// An internal implementation error occurred.
#[fail(display = "{}", _0)]
Setup(SetupError),
/// No field with the specified name was present.
#[fail(display = "Unknown field: {}", _0)]
Field(String),
/// The field was present but was the wrong kind (eg. function, table, global, or memory).
#[fail(display = "Kind error: {}", _0)]
Kind(String),
/// The field was present but was the wrong type (eg. i32, i64, f32, or f64).
#[fail(display = "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 u64. And pack the values into memory
// instead of just using fixed-sized slots.
// Subtract one becase we don't pass the vmctx argument in `values_vec`.
let value_size = mem::size_of::<u64>();
let mut values_vec: Vec<u64> =
vec![0; 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),
}
}
}
// 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.
if let Err(message) = unsafe {
wasmtime_call_trampoline(
callee_vmctx,
exec_code_buf,
values_vec.as_mut_ptr() as *mut u8,
)
} {
return Ok(ActionOutcome::Trapped { message });
}
// 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)),
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 unsafe { instance.lookup_immutable(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 unsafe { instance.lookup_immutable(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
)));
}
})
}
}