Improve error handling, and start refactoring Instance.

Introduce proper error handling in several places, and perform a first
pass at refactoring Instance to make it easier to use.
This commit is contained in:
Dan Gohman
2018-12-07 15:32:51 -05:00
parent fe562297a7
commit 7dcca6be5b
24 changed files with 949 additions and 565 deletions

View File

@@ -1,12 +1,15 @@
//! Support for performing actions with a wasm module from the outside.
use cranelift_codegen::ir;
use link::LinkError;
use std::fmt;
use std::string::String;
use std::vec::Vec;
use wasmtime_environ::CompileError;
/// A runtime value.
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub enum Value {
pub enum RuntimeValue {
/// A runtime value with type i32.
I32(i32),
/// A runtime value with type i64.
@@ -17,61 +20,121 @@ pub enum Value {
F64(u64),
}
impl Value {
/// Return the type of this `Value`.
impl RuntimeValue {
/// Return the type of this `RuntimeValue`.
pub fn value_type(self) -> ir::Type {
match self {
Value::I32(_) => ir::types::I32,
Value::I64(_) => ir::types::I64,
Value::F32(_) => ir::types::F32,
Value::F64(_) => ir::types::F64,
RuntimeValue::I32(_) => ir::types::I32,
RuntimeValue::I64(_) => ir::types::I64,
RuntimeValue::F32(_) => ir::types::F32,
RuntimeValue::F64(_) => ir::types::F64,
}
}
/// Assuming this `Value` holds an `i32`, return that value.
/// Assuming this `RuntimeValue` holds an `i32`, return that value.
pub fn unwrap_i32(self) -> i32 {
match self {
Value::I32(x) => x,
RuntimeValue::I32(x) => x,
_ => panic!("unwrapping value of type {} as i32", self.value_type()),
}
}
/// Assuming this `Value` holds an `i64`, return that value.
/// Assuming this `RuntimeValue` holds an `i64`, return that value.
pub fn unwrap_i64(self) -> i64 {
match self {
Value::I64(x) => x,
RuntimeValue::I64(x) => x,
_ => panic!("unwrapping value of type {} as i64", self.value_type()),
}
}
/// Assuming this `Value` holds an `f32`, return that value.
pub fn unwrap_f32(self) -> u32 {
/// 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 {
Value::F32(x) => x,
RuntimeValue::F32(x) => x,
_ => panic!("unwrapping value of type {} as f32", self.value_type()),
}
}
/// Assuming this `Value` holds an `f64`, return that value.
pub fn unwrap_f64(self) -> u64 {
/// 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 {
Value::F64(x) => x,
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<Value>,
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 {
/// No field with the specified name was present.
#[fail(display = "Unknown field: {}", _0)]
Field(String),
/// An index was out of bounds.
#[fail(display = "Index out of bounds: {}", _0)]
Index(u64),
/// 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),
/// A wasm translation error occured.
#[fail(display = "WebAssembly compilation error: {}", _0)]
Compile(CompileError),
/// Some runtime resource was unavailable or insufficient.
#[fail(display = "Runtime resource error: {}", _0)]
Resource(String),
/// Link error.
#[fail(display = "Link error: {}", _0)]
Link(LinkError),
/// Start function trapped.
#[fail(display = "Start function trapped: {}", _0)]
Start(String),
}

View File

@@ -28,9 +28,12 @@ impl Code {
}
/// Allocate `size` bytes of memory which can be made executable later by
/// calling `publish()`.
/// TODO: alignment
pub fn allocate(&mut self, size: usize) -> Result<*mut u8, String> {
/// calling `publish()`. Note that we allocate the memory as writeable so
/// that it can be written to and patched, though we make it readonly before
/// actually executing from it.
///
/// TODO: Add an alignment flag.
fn allocate(&mut self, size: usize) -> Result<*mut u8, String> {
if self.current.len() - self.position < size {
self.mmaps.push(mem::replace(
&mut self.current,
@@ -63,8 +66,8 @@ impl Code {
if !m.as_ptr().is_null() {
unsafe {
region::protect(m.as_mut_ptr(), m.len(), region::Protection::ReadExecute)
.expect("unable to make memory readonly");
}
.expect("unable to make memory readonly and executable");
}
}
self.published = self.mmaps.len();

View File

@@ -8,7 +8,7 @@ pub enum ExportValue {
/// A function export value.
Function {
/// The address of the native-code function.
address: usize,
address: *const u8,
/// The function signature declaration, used for compatibilty checking.
signature: ir::Signature,
},
@@ -40,7 +40,7 @@ pub enum ExportValue {
impl ExportValue {
/// Construct a function export value.
pub fn function(address: usize, signature: ir::Signature) -> Self {
pub fn function(address: *const u8, signature: ir::Signature) -> Self {
ExportValue::Function { address, signature }
}

View File

@@ -1,39 +1,65 @@
//! Support for reading the value of a wasm global from outside the module.
use action::Value;
use action::{ActionError, RuntimeValue};
use cranelift_codegen::ir;
use cranelift_entity::EntityRef;
use cranelift_wasm::GlobalIndex;
use std::string::String;
use vmcontext::VMContext;
use instance::Instance;
use wasmtime_environ::{Export, Module};
/// Jumps to the code region of memory and invoke the exported function
pub fn get(module: &Module, vmctx: *mut VMContext, global_name: &str) -> Result<Value, String> {
/// Reads the value of the named global variable in `module`.
pub fn get(
module: &Module,
instance: &mut Instance,
global_name: &str,
) -> Result<RuntimeValue, ActionError> {
let global_index = match module.exports.get(global_name) {
Some(Export::Global(index)) => *index,
Some(_) => return Err(format!("exported item \"{}\" is not a global", global_name)),
None => return Err(format!("no export named \"{}\"", global_name)),
Some(_) => {
return Err(ActionError::Kind(format!(
"exported item \"{}\" is not a global",
global_name
)))
}
None => {
return Err(ActionError::Field(format!(
"no export named \"{}\"",
global_name
)))
}
};
get_by_index(module, vmctx, global_index)
get_by_index(module, instance, global_index)
}
/// Reads the value of the indexed global variable in `module`.
pub fn get_by_index(
module: &Module,
vmctx: *mut VMContext,
instance: &mut Instance,
global_index: GlobalIndex,
) -> Result<Value, String> {
// TODO: Return Err if the index is out of bounds.
) -> Result<RuntimeValue, ActionError> {
unsafe {
let vmctx = &mut *vmctx;
let vmctx = &mut *instance.vmctx();
let vmglobal = vmctx.global(global_index);
let definition = vmglobal.get_definition(module.is_imported_global(global_index));
Ok(match module.globals[global_index].ty {
ir::types::I32 => Value::I32(*definition.as_i32()),
ir::types::I64 => Value::I64(*definition.as_i64()),
ir::types::F32 => Value::F32(*definition.as_f32_bits()),
ir::types::F64 => Value::F64(*definition.as_f64_bits()),
other => return Err(format!("global with type {} not supported", other)),
})
Ok(
match module
.globals
.get(global_index)
.ok_or_else(|| ActionError::Index(global_index.index() as u64))?
.ty
{
ir::types::I32 => RuntimeValue::I32(*definition.as_i32()),
ir::types::I64 => RuntimeValue::I64(*definition.as_i64()),
ir::types::F32 => RuntimeValue::F32(*definition.as_f32_bits()),
ir::types::F64 => RuntimeValue::F64(*definition.as_f64_bits()),
other => {
return Err(ActionError::Type(format!(
"global with type {} not supported",
other
)))
}
},
)
}
}

View File

@@ -0,0 +1,30 @@
use cranelift_entity::PrimaryMap;
use cranelift_wasm::{FuncIndex, GlobalIndex, MemoryIndex, TableIndex};
use vmcontext::{VMGlobal, VMMemory, VMTable};
/// Resolved import pointers.
#[derive(Debug)]
pub struct Imports {
/// Resolved addresses for imported functions.
pub functions: PrimaryMap<FuncIndex, *const u8>,
/// Resolved addresses for imported tables.
pub tables: PrimaryMap<TableIndex, *mut VMTable>,
/// Resolved addresses for imported globals.
pub globals: PrimaryMap<GlobalIndex, *mut VMGlobal>,
/// Resolved addresses for imported memories.
pub memories: PrimaryMap<MemoryIndex, *mut VMMemory>,
}
impl Imports {
pub fn new() -> Self {
Self {
functions: PrimaryMap::new(),
tables: PrimaryMap::new(),
globals: PrimaryMap::new(),
memories: PrimaryMap::new(),
}
}
}

View File

@@ -3,14 +3,16 @@
use cranelift_entity::EntityRef;
use cranelift_entity::PrimaryMap;
use cranelift_wasm::{GlobalIndex, MemoryIndex, TableIndex};
use cranelift_wasm::{DefinedFuncIndex, FuncIndex, GlobalIndex, MemoryIndex, TableIndex};
use imports::Imports;
use memory::LinearMemory;
use sig_registry::SignatureRegistry;
use std::ptr;
use std::slice;
use std::string::String;
use table::Table;
use vmcontext::{VMCallerCheckedAnyfunc, VMContext, VMGlobal, VMMemory, VMTable};
use wasmtime_environ::{Compilation, DataInitializer, Module};
use wasmtime_environ::{DataInitializer, Module};
/// An Instance of a WebAssemby module.
#[derive(Debug)]
@@ -34,20 +36,29 @@ pub struct Instance {
/// Table storage base address vector pointed to by vmctx.
vmctx_tables: PrimaryMap<TableIndex, VMTable>,
/// Pointer values for resolved imports.
imports: Imports,
/// Pointers to functions in executable memory.
allocated_functions: PrimaryMap<DefinedFuncIndex, (*mut u8, usize)>,
/// Context pointer used by JIT code.
vmctx: VMContext,
}
impl Instance {
/// Create a new `Instance`.
/// Create a new `Instance`. In order to complete instantiation, call
/// `invoke_start_function`. `allocated_functions` holds the function bodies
/// which have been placed in executable memory.
pub fn new(
module: &Module,
compilation: &Compilation,
allocated_functions: PrimaryMap<DefinedFuncIndex, (*mut u8, usize)>,
data_initializers: &[DataInitializer],
imports: Imports,
) -> Result<Self, String> {
let mut sig_registry = instantiate_signatures(module);
let mut memories = instantiate_memories(module, data_initializers)?;
let mut tables = instantiate_tables(module, compilation, &mut sig_registry);
let mut tables = instantiate_tables(module, &allocated_functions, &mut sig_registry);
let mut vmctx_memories = memories
.values_mut()
@@ -73,6 +84,8 @@ impl Instance {
vmctx_memories,
vmctx_globals,
vmctx_tables,
imports,
allocated_functions,
vmctx: VMContext::new(
vmctx_memories_ptr,
vmctx_globals_ptr,
@@ -83,15 +96,27 @@ impl Instance {
}
/// Return the vmctx pointer to be passed into JIT code.
pub fn vmctx(&mut self) -> *mut VMContext {
&mut self.vmctx as *mut VMContext
pub fn vmctx(&mut self) -> &mut VMContext {
&mut self.vmctx
}
/// Return the offset from the vmctx pointer to its containing Instance.
pub fn vmctx_offset() -> isize {
pub(crate) fn vmctx_offset() -> isize {
offset_of!(Self, vmctx) as isize
}
/// Return the pointer to executable memory for the given function index.
pub(crate) fn get_allocated_function(&self, index: DefinedFuncIndex) -> Option<&[u8]> {
self.allocated_functions
.get(index)
.map(|(ptr, len)| unsafe { slice::from_raw_parts(*ptr, *len) })
}
/// Return the pointer to executable memory for the given function index.
pub(crate) fn get_imported_function(&self, index: FuncIndex) -> Option<*const u8> {
self.imports.functions.get(index).cloned()
}
/// Grow memory by the specified amount of pages.
///
/// Returns `None` if memory can't be grown by the specified amount
@@ -163,7 +188,7 @@ fn instantiate_memories(
/// Allocate memory for just the tables of the current module.
fn instantiate_tables(
module: &Module,
compilation: &Compilation,
allocated_functions: &PrimaryMap<DefinedFuncIndex, (*mut u8, usize)>,
sig_registry: &mut SignatureRegistry,
) -> PrimaryMap<TableIndex, Table> {
let mut tables = PrimaryMap::with_capacity(module.table_plans.len());
@@ -177,14 +202,12 @@ fn instantiate_tables(
let subslice = &mut slice[init.offset..init.offset + init.elements.len()];
for (i, func_idx) in init.elements.iter().enumerate() {
let callee_sig = module.functions[*func_idx];
let code_buf = &compilation.functions[module
let func_ptr = allocated_functions[module
.defined_func_index(*func_idx)
.expect("table element initializer with imported function not supported yet")];
.expect("table element initializer with imported function not supported yet")]
.0;
let type_id = sig_registry.lookup(callee_sig);
subslice[i] = VMCallerCheckedAnyfunc {
func_ptr: code_buf.as_ptr(),
type_id,
};
subslice[i] = VMCallerCheckedAnyfunc { func_ptr, type_id };
}
}

View File

@@ -1,55 +1,87 @@
//! Support for invoking wasm functions from outside a wasm module.
use action::{ActionOutcome, Value};
use action::{ActionError, ActionOutcome, RuntimeValue};
use code::Code;
use cranelift_codegen::ir::InstBuilder;
use cranelift_codegen::{binemit, ir, isa, Context};
use cranelift_entity::EntityRef;
use cranelift_frontend::{FunctionBuilder, FunctionBuilderContext};
use cranelift_wasm::FuncIndex;
use instance::Instance;
use signalhandlers::{ensure_eager_signal_handlers, ensure_full_signal_handlers, TrapContext};
use std::mem;
use std::ptr;
use std::string::String;
use std::vec::Vec;
use traphandlers::call_wasm;
use vmcontext::VMContext;
use wasmtime_environ::{Compilation, Export, Module, RelocSink};
use wasmtime_environ::{CompileError, Export, Module, RelocSink};
/// Jumps to the code region of memory and invoke the exported function
/// Calls the given named function, passing its return values and returning
/// its results.
pub fn invoke(
code: &mut Code,
isa: &isa::TargetIsa,
module: &Module,
compilation: &Compilation,
vmctx: *mut VMContext,
instance: &mut Instance,
function: &str,
args: &[Value],
) -> Result<ActionOutcome, String> {
args: &[RuntimeValue],
) -> Result<ActionOutcome, ActionError> {
let fn_index = match module.exports.get(function) {
Some(Export::Function(index)) => *index,
Some(_) => return Err(format!("exported item \"{}\" is not a function", function)),
None => return Err(format!("no export named \"{}\"", function)),
Some(_) => {
return Err(ActionError::Kind(format!(
"exported item \"{}\" is not a function",
function
)))
}
None => {
return Err(ActionError::Field(format!(
"no export named \"{}\"",
function
)))
}
};
invoke_by_index(code, isa, module, compilation, vmctx, fn_index, args)
invoke_by_index(code, isa, module, instance, fn_index, args)
}
/// Invoke the WebAssembly start function of the instance, if one is present.
pub fn invoke_start_function(
code: &mut Code,
isa: &isa::TargetIsa,
module: &Module,
instance: &mut Instance,
) -> Result<ActionOutcome, ActionError> {
if let Some(start_index) = module.start_func {
invoke_by_index(code, isa, module, instance, start_index, &[])
} else {
// No start function, just return nothing.
Ok(ActionOutcome::Returned { values: vec![] })
}
}
/// Calls the given indexed function, passing its return values and returning
/// its results.
pub fn invoke_by_index(
code: &mut Code,
isa: &isa::TargetIsa,
module: &Module,
compilation: &Compilation,
vmctx: *mut VMContext,
instance: &mut Instance,
fn_index: FuncIndex,
args: &[Value],
) -> Result<ActionOutcome, String> {
// TODO: Return Err if fn_index is out of bounds.
args: &[RuntimeValue],
) -> Result<ActionOutcome, ActionError> {
let exec_code_buf = match module.defined_func_index(fn_index) {
Some(def_fn_index) => {
let code_buf = &compilation.functions[def_fn_index];
code.allocate_copy_of_slice(&code_buf)?.as_ptr() as usize
let slice = instance
.get_allocated_function(def_fn_index)
.ok_or_else(|| ActionError::Index(def_fn_index.index() as u64))?;
code.allocate_copy_of_slice(slice)
.map_err(ActionError::Resource)?
.as_ptr()
}
None => compilation.resolved_func_imports[fn_index],
None => instance
.get_imported_function(fn_index)
.ok_or_else(|| ActionError::Index(fn_index.index() as u64))?,
};
let sig = &module.signatures[module.functions[fn_index]];
@@ -68,20 +100,24 @@ pub fn invoke_by_index(
ensure_eager_signal_handlers();
ensure_full_signal_handlers(&mut traps);
if !traps.haveSignalHandlers {
return Err("failed to install signal handlers".to_string());
return Err(ActionError::Resource(
"failed to install signal handlers".to_string(),
));
}
call_through_wrapper(code, isa, exec_code_buf, vmctx, args, &sig)
call_through_wrapper(code, isa, exec_code_buf, instance, args, &sig)
}
fn call_through_wrapper(
code: &mut Code,
isa: &isa::TargetIsa,
callee: usize,
vmctx: *mut VMContext,
args: &[Value],
callee: *const u8,
instance: &mut Instance,
args: &[RuntimeValue],
sig: &ir::Signature,
) -> Result<ActionOutcome, String> {
) -> Result<ActionOutcome, ActionError> {
let vmctx = instance.vmctx() as *mut VMContext;
for (index, value) in args.iter().enumerate() {
assert_eq!(value.value_type(), sig.params[index].value_type);
}
@@ -111,16 +147,16 @@ fn call_through_wrapper(
for value in args {
match value {
Value::I32(i) => {
RuntimeValue::I32(i) => {
callee_args.push(builder.ins().iconst(ir::types::I32, i64::from(*i)))
}
Value::I64(i) => callee_args.push(builder.ins().iconst(ir::types::I64, *i)),
Value::F32(i) => callee_args.push(
RuntimeValue::I64(i) => callee_args.push(builder.ins().iconst(ir::types::I64, *i)),
RuntimeValue::F32(i) => callee_args.push(
builder
.ins()
.f32const(ir::immediates::Ieee32::with_bits(*i)),
),
Value::F64(i) => callee_args.push(
RuntimeValue::F64(i) => callee_args.push(
builder
.ins()
.f64const(ir::immediates::Ieee64::with_bits(*i)),
@@ -162,10 +198,13 @@ fn call_through_wrapper(
let mut trap_sink = binemit::NullTrapSink {};
context
.compile_and_emit(isa, &mut code_buf, &mut reloc_sink, &mut trap_sink)
.map_err(|e| e.to_string())?;
.map_err(|error| ActionError::Compile(CompileError::Codegen(error)))?;
assert!(reloc_sink.func_relocs.is_empty());
let exec_code_buf = code.allocate_copy_of_slice(&code_buf)?.as_ptr();
let exec_code_buf = code
.allocate_copy_of_slice(&code_buf)
.map_err(ActionError::Resource)?
.as_ptr();
code.publish();
let func = unsafe { mem::transmute::<_, fn()>(exec_code_buf) };
@@ -179,10 +218,10 @@ fn call_through_wrapper(
let ptr = results_vec.as_ptr().add(index * value_size);
match abi_param.value_type {
ir::types::I32 => Value::I32(ptr::read(ptr as *const i32)),
ir::types::I64 => Value::I64(ptr::read(ptr as *const i64)),
ir::types::F32 => Value::F32(ptr::read(ptr as *const u32)),
ir::types::F64 => Value::F64(ptr::read(ptr as *const u64)),
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),
}
};

View File

@@ -40,15 +40,19 @@ extern crate libc;
#[macro_use]
extern crate memoffset;
extern crate cast;
extern crate failure;
#[macro_use]
extern crate failure_derive;
mod action;
mod code;
mod execute;
mod export;
mod get;
mod imports;
mod instance;
mod invoke;
mod libcalls;
mod link;
mod memory;
mod mmap;
mod sig_registry;
@@ -58,13 +62,13 @@ mod traphandlers;
mod vmcontext;
mod world;
pub use action::{ActionOutcome, Value};
pub use action::{ActionError, ActionOutcome, RuntimeValue};
pub use code::Code;
pub use execute::{compile_and_link_module, finish_instantiation};
pub use export::{ExportValue, NullResolver, Resolver};
pub use get::get;
pub use get::{get, get_by_index};
pub use instance::Instance;
pub use invoke::invoke;
pub use invoke::{invoke, invoke_by_index, invoke_start_function};
pub use link::link_module;
pub use traphandlers::{call_wasm, LookupCodeSegment, RecordTrap, Unwind};
pub use vmcontext::{VMContext, VMGlobal, VMMemory, VMTable};
pub use world::InstanceWorld;

View File

@@ -1,153 +1,162 @@
//! TODO: Move the contents of this file to other files, as "execute.rs" is
//! no longer a descriptive filename.
use action::ActionOutcome;
use code::Code;
use cranelift_codegen::binemit::Reloc;
use cranelift_codegen::isa::TargetIsa;
use cranelift_entity::{EntityRef, PrimaryMap};
use cranelift_wasm::{
DefinedFuncIndex, Global, GlobalInit, Memory, MemoryIndex, Table, TableElementType,
};
use export::{ExportValue, Resolver};
use instance::Instance;
use invoke::invoke_by_index;
use region::{protect, Protection};
use imports::Imports;
use std::ptr::write_unaligned;
use std::string::String;
use std::vec::Vec;
use vmcontext::VMContext;
use vmcontext::{VMGlobal, VMMemory, VMTable};
use wasmtime_environ::{
compile_module, Compilation, MemoryPlan, MemoryStyle, Module, ModuleTranslation, Relocation,
RelocationTarget, TablePlan, TableStyle,
MemoryPlan, MemoryStyle, Module, Relocation, RelocationTarget, Relocations, TablePlan,
TableStyle,
};
/// Executes a module that has been translated with the `wasmtime-environ` environment
/// implementation.
pub fn compile_and_link_module<'data, 'module>(
isa: &TargetIsa,
translation: &ModuleTranslation<'data, 'module>,
resolver: &mut Resolver,
) -> Result<Compilation, String> {
let (mut compilation, relocations) = compile_module(&translation, isa)?;
/// A link error, such as incompatible or unmatched imports/exports.
#[derive(Fail, Debug)]
#[fail(display = "Link error: {}", _0)]
pub struct LinkError(String);
for (index, (ref module, ref field)) in translation.module.imported_funcs.iter() {
match resolver.resolve(module, field) {
/// Links a module that has been compiled with `compiled_module` in `wasmtime-environ`.
pub fn link_module(
module: &Module,
allocated_functions: &PrimaryMap<DefinedFuncIndex, (*mut u8, usize)>,
relocations: Relocations,
resolver: &mut Resolver,
) -> Result<Imports, LinkError> {
let mut imports = Imports::new();
for (index, (ref module_name, ref field)) in module.imported_funcs.iter() {
match resolver.resolve(module_name, field) {
Some(export_value) => match export_value {
ExportValue::Function { address, signature } => {
let import_signature =
&translation.module.signatures[translation.module.functions[index]];
let import_signature = &module.signatures[module.functions[index]];
if signature != *import_signature {
return Err(format!(
"{}/{}: exported function with signature {} incompatible with function import with signature {}",
module, field,
signature, import_signature,
return Err(LinkError(
format!("{}/{}: exported function with signature {} incompatible with function import with signature {}",
module_name, field,
signature, import_signature)
));
}
compilation.resolved_func_imports.push(address);
imports.functions.push(address);
}
ExportValue::Table { .. }
| ExportValue::Memory { .. }
| ExportValue::Global { .. } => {
return Err(format!(
return Err(LinkError(format!(
"{}/{}: export not compatible with function import",
module, field
));
module_name, field
)));
}
},
None => return Err(format!("{}/{}: no provided import function", module, field)),
None => {
return Err(LinkError(format!(
"{}/{}: no provided import function",
module_name, field
)))
}
}
}
for (index, (ref module, ref field)) in translation.module.imported_globals.iter() {
match resolver.resolve(module, field) {
for (index, (ref module_name, ref field)) in module.imported_globals.iter() {
match resolver.resolve(module_name, field) {
Some(export_value) => match export_value {
ExportValue::Global { address, global } => {
let imported_global = translation.module.globals[index];
let imported_global = module.globals[index];
if !is_global_compatible(&global, &imported_global) {
return Err(format!(
return Err(LinkError(format!(
"{}/{}: exported global incompatible with global import",
module, field,
));
module_name, field
)));
}
compilation.resolved_global_imports.push(address as usize);
imports.globals.push(address as *mut VMGlobal);
}
ExportValue::Table { .. }
| ExportValue::Memory { .. }
| ExportValue::Function { .. } => {
return Err(format!(
return Err(LinkError(format!(
"{}/{}: exported global incompatible with global import",
module, field
));
module_name, field
)));
}
},
None => {
return Err(format!(
return Err(LinkError(format!(
"no provided import global for {}/{}",
module, field
))
module_name, field
)))
}
}
}
for (index, (ref module, ref field)) in translation.module.imported_tables.iter() {
match resolver.resolve(module, field) {
for (index, (ref module_name, ref field)) in module.imported_tables.iter() {
match resolver.resolve(module_name, field) {
Some(export_value) => match export_value {
ExportValue::Table { address, table } => {
let import_table = &translation.module.table_plans[index];
let import_table = &module.table_plans[index];
if !is_table_compatible(&table, import_table) {
return Err(format!(
return Err(LinkError(format!(
"{}/{}: exported table incompatible with table import",
module, field,
));
module_name, field,
)));
}
compilation.resolved_table_imports.push(address as usize);
imports.tables.push(address as *mut VMTable);
}
ExportValue::Global { .. }
| ExportValue::Memory { .. }
| ExportValue::Function { .. } => {
return Err(format!(
return Err(LinkError(format!(
"{}/{}: export not compatible with table import",
module, field
));
module_name, field
)));
}
},
None => return Err(format!("no provided import table for {}/{}", module, field)),
None => {
return Err(LinkError(format!(
"no provided import table for {}/{}",
module_name, field
)))
}
}
}
for (index, (ref module, ref field)) in translation.module.imported_memories.iter() {
match resolver.resolve(module, field) {
for (index, (ref module_name, ref field)) in module.imported_memories.iter() {
match resolver.resolve(module_name, field) {
Some(export_value) => match export_value {
ExportValue::Memory { address, memory } => {
let import_memory = &translation.module.memory_plans[index];
let import_memory = &module.memory_plans[index];
if is_memory_compatible(&memory, import_memory) {
return Err(format!(
return Err(LinkError(format!(
"{}/{}: exported memory incompatible with memory import",
module, field
));
module_name, field
)));
}
compilation.resolved_memory_imports.push(address as usize);
imports.memories.push(address as *mut VMMemory);
}
ExportValue::Table { .. }
| ExportValue::Global { .. }
| ExportValue::Function { .. } => {
return Err(format!(
return Err(LinkError(format!(
"{}/{}: export not compatible with memory import",
module, field
));
module_name, field
)));
}
},
None => {
return Err(format!(
return Err(LinkError(format!(
"no provided import memory for {}/{}",
module, field
))
module_name, field
)))
}
}
}
// Apply relocations, now that we have virtual addresses for everything.
relocate(&mut compilation, &relocations, &translation.module)?;
relocate(&imports, allocated_functions, relocations, &module);
Ok(compilation)
Ok(imports)
}
fn is_global_compatible(exported: &Global, imported: &Global) -> bool {
@@ -265,23 +274,19 @@ fn is_memory_compatible(exported: &MemoryPlan, imported: &MemoryPlan) -> bool {
&& exported_offset_guard_size >= imported_offset_guard_size
}
extern "C" {
pub fn __rust_probestack();
}
/// Performs the relocations inside the function bytecode, provided the necessary metadata.
fn relocate(
compilation: &mut Compilation,
relocations: &PrimaryMap<DefinedFuncIndex, Vec<Relocation>>,
imports: &Imports,
allocated_functions: &PrimaryMap<DefinedFuncIndex, (*mut u8, usize)>,
relocations: PrimaryMap<DefinedFuncIndex, Vec<Relocation>>,
module: &Module,
) -> Result<(), String> {
// The relocations are relative to the relocation's address plus four bytes.
for (i, function_relocs) in relocations.iter() {
) {
for (i, function_relocs) in relocations.into_iter() {
for r in function_relocs {
let target_func_address: usize = match r.reloc_target {
RelocationTarget::UserFunc(index) => match module.defined_func_index(index) {
Some(f) => compilation.functions[f].as_ptr() as usize,
None => compilation.resolved_func_imports[index],
Some(f) => allocated_functions[f].0 as usize,
None => imports.functions[index] as usize,
},
RelocationTarget::MemoryGrow => wasmtime_memory_grow as usize,
RelocationTarget::MemorySize => wasmtime_memory_size as usize,
@@ -303,11 +308,11 @@ fn relocate(
}
};
let body = &mut compilation.functions[i];
let body = allocated_functions[i].0;
match r.reloc {
#[cfg(target_pointer_width = "64")]
Reloc::Abs8 => unsafe {
let reloc_address = body.as_mut_ptr().add(r.offset as usize) as usize;
let reloc_address = body.add(r.offset as usize) as usize;
let reloc_addend = r.addend as isize;
let reloc_abs = (target_func_address as u64)
.checked_add(reloc_addend as u64)
@@ -316,7 +321,7 @@ fn relocate(
},
#[cfg(target_pointer_width = "32")]
Reloc::X86PCRel4 => unsafe {
let reloc_address = body.as_mut_ptr().add(r.offset as usize) as usize;
let reloc_address = body.add(r.offset as usize) as usize;
let reloc_addend = r.addend as isize;
let reloc_delta_u32 = (target_func_address as u32)
.wrapping_sub(reloc_address as u32)
@@ -328,9 +333,15 @@ fn relocate(
}
}
}
Ok(())
}
/// A declaration for the stack probe function in Rust's standard library, for
/// catching callstack overflow.
extern "C" {
pub fn __rust_probestack();
}
/// The implementation of memory.grow.
extern "C" fn wasmtime_memory_grow(size: u32, memory_index: u32, vmctx: *mut VMContext) -> u32 {
let instance = unsafe { (&mut *vmctx).instance() };
let memory_index = MemoryIndex::new(memory_index as usize);
@@ -340,53 +351,10 @@ extern "C" fn wasmtime_memory_grow(size: u32, memory_index: u32, vmctx: *mut VMC
.unwrap_or(u32::max_value())
}
/// The implementation of memory.size.
extern "C" fn wasmtime_memory_size(memory_index: u32, vmctx: *mut VMContext) -> u32 {
let instance = unsafe { (&mut *vmctx).instance() };
let memory_index = MemoryIndex::new(memory_index as usize);
instance.memory_size(memory_index)
}
/// prepares the execution context
pub fn finish_instantiation(
code: &mut Code,
isa: &TargetIsa,
module: &Module,
compilation: &Compilation,
instance: &mut Instance,
) -> Result<(), String> {
// TODO: Put all the function bodies into a page-aligned memory region, and
// then make them ReadExecute rather than ReadWriteExecute.
for code_buf in compilation.functions.values() {
match unsafe {
protect(
code_buf.as_ptr(),
code_buf.len(),
Protection::ReadWriteExecute,
)
} {
Ok(()) => (),
Err(err) => {
return Err(format!(
"failed to give executable permission to code: {}",
err
))
}
}
}
if let Some(start_index) = module.start_func {
let vmctx = instance.vmctx();
let result = invoke_by_index(code, isa, module, compilation, vmctx, start_index, &[])?;
match result {
ActionOutcome::Returned { values } => {
assert!(values.is_empty());
}
ActionOutcome::Trapped { message } => {
return Err(format!("start function trapped: {}", message));
}
}
}
Ok(())
}

View File

@@ -50,8 +50,8 @@ impl LinearMemory {
inaccessible_bytes,
region::Protection::None,
)
.expect("unable to make memory inaccessible");
}
.expect("unable to make memory inaccessible");
Ok(Self {
mmap,

View File

@@ -1,15 +1,18 @@
use action::{ActionOutcome, Value};
use action::{ActionError, ActionOutcome, RuntimeValue};
use code::Code;
use cranelift_codegen::isa;
use cranelift_wasm::{GlobalIndex, MemoryIndex};
use execute::{compile_and_link_module, finish_instantiation};
use cranelift_entity::PrimaryMap;
use cranelift_wasm::{DefinedFuncIndex, GlobalIndex, MemoryIndex};
use export::Resolver;
use get::get;
use instance::Instance;
use invoke::invoke;
use invoke::{invoke, invoke_start_function};
use link::link_module;
use std::str;
use vmcontext::VMGlobal;
use wasmtime_environ::{Compilation, Module, ModuleEnvironment, Tunables};
use wasmtime_environ::{
compile_module, Compilation, CompileError, Module, ModuleEnvironment, Tunables,
};
/// A module, an instance of that module, and accompanying compilation artifacts.
///
@@ -17,7 +20,6 @@ use wasmtime_environ::{Compilation, Module, ModuleEnvironment, Tunables};
pub struct InstanceWorld {
module: Module,
instance: Instance,
compilation: Compilation,
}
impl InstanceWorld {
@@ -27,34 +29,61 @@ impl InstanceWorld {
isa: &isa::TargetIsa,
data: &[u8],
resolver: &mut Resolver,
) -> Result<Self, String> {
) -> Result<Self, ActionError> {
let mut module = Module::new();
// TODO: Allow the tunables to be overridden.
let tunables = Tunables::default();
let (instance, compilation) = {
let translation = {
let environ = ModuleEnvironment::new(isa, &mut module, tunables);
let instance = {
// TODO: Untie this.
let ((mut compilation, relocations), lazy_data_initializers) = {
let (lazy_function_body_inputs, lazy_data_initializers) = {
let environ = ModuleEnvironment::new(isa, &mut module, tunables);
environ.translate(&data).map_err(|e| e.to_string())?
let translation = environ
.translate(&data)
.map_err(|error| ActionError::Compile(CompileError::Wasm(error)))?;
(
translation.lazy.function_body_inputs,
translation.lazy.data_initializers,
)
};
(
compile_module(&module, &lazy_function_body_inputs, isa)
.map_err(ActionError::Compile)?,
lazy_data_initializers,
)
};
let compilation = compile_and_link_module(isa, &translation, resolver)?;
let allocated_functions =
allocate_functions(code, compilation).map_err(ActionError::Resource)?;
let resolved = link_module(&module, &allocated_functions, relocations, resolver)
.map_err(ActionError::Link)?;
let mut instance = Instance::new(
translation.module,
&compilation,
&translation.lazy.data_initializers,
)?;
&module,
allocated_functions,
&lazy_data_initializers,
resolved,
)
.map_err(ActionError::Resource)?;
finish_instantiation(code, isa, &translation.module, &compilation, &mut instance)?;
// The WebAssembly spec specifies that the start function is
// invoked automatically at instantiation time.
match invoke_start_function(code, isa, &module, &mut instance)? {
ActionOutcome::Returned { .. } => {}
ActionOutcome::Trapped { message } => {
// Instantiation fails if the start function traps.
return Err(ActionError::Start(message));
}
}
(instance, compilation)
instance
};
Ok(Self {
module,
instance,
compilation,
})
Ok(Self { module, instance })
}
/// Invoke a function in this `InstanceWorld` by name.
@@ -63,23 +92,21 @@ impl InstanceWorld {
code: &mut Code,
isa: &isa::TargetIsa,
function_name: &str,
args: &[Value],
) -> Result<ActionOutcome, String> {
args: &[RuntimeValue],
) -> Result<ActionOutcome, ActionError> {
invoke(
code,
isa,
&self.module,
&self.compilation,
self.instance.vmctx(),
&mut self.instance,
&function_name,
args,
)
.map_err(|e| e.to_string())
}
/// Read a global in this `InstanceWorld` by name.
pub fn get(&mut self, global_name: &str) -> Result<Value, String> {
get(&self.module, self.instance.vmctx(), global_name).map_err(|e| e.to_string())
pub fn get(&mut self, global_name: &str) -> Result<RuntimeValue, ActionError> {
get(&self.module, &mut self.instance, global_name)
}
/// Returns a slice of the contents of allocated linear memory.
@@ -92,3 +119,15 @@ impl InstanceWorld {
self.instance.inspect_global(global_index)
}
}
fn allocate_functions(
code: &mut Code,
compilation: Compilation,
) -> Result<PrimaryMap<DefinedFuncIndex, (*mut u8, usize)>, String> {
let mut result = PrimaryMap::with_capacity(compilation.functions.len());
for (_, body) in compilation.functions.into_iter() {
let slice = code.allocate_copy_of_slice(&body)?;
result.push((slice.as_mut_ptr(), slice.len()));
}
Ok(result)
}