Implement the remaining valid spec tests.
And lots of other miscellaneous changes. Rename InstanceWorld to InstancePlus and reorganize its contents. This still isn't a great name, but at least now it has a clear purpose.
This commit is contained in:
@@ -6,6 +6,7 @@ use std::fmt;
|
||||
use std::string::String;
|
||||
use std::vec::Vec;
|
||||
use wasmtime_environ::CompileError;
|
||||
use wasmtime_runtime::InstantiationError;
|
||||
|
||||
/// A runtime value.
|
||||
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
|
||||
@@ -110,10 +111,6 @@ pub enum ActionError {
|
||||
#[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),
|
||||
@@ -126,9 +123,10 @@ pub enum ActionError {
|
||||
#[fail(display = "WebAssembly compilation error: {}", _0)]
|
||||
Compile(CompileError),
|
||||
|
||||
/// Some runtime resource was unavailable or insufficient.
|
||||
#[fail(display = "Runtime resource error: {}", _0)]
|
||||
Resource(String),
|
||||
/// Some runtime resource was unavailable or insufficient, or the start function
|
||||
/// trapped.
|
||||
#[fail(display = "Instantiation error: {}", _0)]
|
||||
Instantiate(InstantiationError),
|
||||
|
||||
/// Link error.
|
||||
#[fail(display = "Link error: {}", _0)]
|
||||
|
||||
@@ -1,97 +0,0 @@
|
||||
use cranelift_codegen::ir;
|
||||
use cranelift_wasm::Global;
|
||||
use wasmtime_environ::{MemoryPlan, TablePlan};
|
||||
use wasmtime_runtime::{
|
||||
VMContext, VMFunctionBody, VMGlobalDefinition, VMMemoryDefinition, VMTableDefinition,
|
||||
};
|
||||
|
||||
/// An exported function.
|
||||
pub struct FunctionExport {
|
||||
/// The address of the native-code function.
|
||||
pub address: *const VMFunctionBody,
|
||||
/// The function signature declaration, used for compatibilty checking.
|
||||
pub signature: ir::Signature,
|
||||
}
|
||||
|
||||
/// The value of an export passed from one instance to another.
|
||||
pub enum Export {
|
||||
/// A function export value.
|
||||
Function(FunctionExport),
|
||||
|
||||
/// A table export value.
|
||||
Table {
|
||||
/// The address of the table descriptor.
|
||||
address: *mut VMTableDefinition,
|
||||
/// Pointer to the containing VMContext.
|
||||
vmctx: *mut VMContext,
|
||||
/// The table declaration, used for compatibilty checking.
|
||||
table: TablePlan,
|
||||
},
|
||||
|
||||
/// A memory export value.
|
||||
Memory {
|
||||
/// The address of the memory descriptor.
|
||||
address: *mut VMMemoryDefinition,
|
||||
/// Pointer to the containing VMContext.
|
||||
vmctx: *mut VMContext,
|
||||
/// The memory declaration, used for compatibilty checking.
|
||||
memory: MemoryPlan,
|
||||
},
|
||||
|
||||
/// A global export value.
|
||||
Global {
|
||||
/// The address of the global storage.
|
||||
address: *mut VMGlobalDefinition,
|
||||
/// The global declaration, used for compatibilty checking.
|
||||
global: Global,
|
||||
},
|
||||
}
|
||||
|
||||
impl Export {
|
||||
/// Construct a function export value.
|
||||
pub fn function(address: *const VMFunctionBody, signature: ir::Signature) -> Self {
|
||||
Export::Function(FunctionExport { address, signature })
|
||||
}
|
||||
|
||||
/// Construct a table export value.
|
||||
pub fn table(address: *mut VMTableDefinition, vmctx: *mut VMContext, table: TablePlan) -> Self {
|
||||
Export::Table {
|
||||
address,
|
||||
vmctx,
|
||||
table,
|
||||
}
|
||||
}
|
||||
|
||||
/// Construct a memory export value.
|
||||
pub fn memory(
|
||||
address: *mut VMMemoryDefinition,
|
||||
vmctx: *mut VMContext,
|
||||
memory: MemoryPlan,
|
||||
) -> Self {
|
||||
Export::Memory {
|
||||
address,
|
||||
vmctx,
|
||||
memory,
|
||||
}
|
||||
}
|
||||
|
||||
/// Construct a global export value.
|
||||
pub fn global(address: *mut VMGlobalDefinition, global: Global) -> Self {
|
||||
Export::Global { address, global }
|
||||
}
|
||||
}
|
||||
|
||||
/// Import resolver connects imports with available exported values.
|
||||
pub trait Resolver {
|
||||
/// Resolve the given module/field combo.
|
||||
fn resolve(&mut self, module: &str, field: &str) -> Option<Export>;
|
||||
}
|
||||
|
||||
/// `Resolver` implementation that always resolves to `None`.
|
||||
pub struct NullResolver {}
|
||||
|
||||
impl Resolver for NullResolver {
|
||||
fn resolve(&mut self, _module: &str, _field: &str) -> Option<Export> {
|
||||
None
|
||||
}
|
||||
}
|
||||
287
lib/execute/src/instance_plus.rs
Normal file
287
lib/execute/src/instance_plus.rs
Normal file
@@ -0,0 +1,287 @@
|
||||
use action::{ActionError, ActionOutcome, RuntimeValue};
|
||||
use cranelift_codegen::{ir, isa};
|
||||
use cranelift_entity::{BoxedSlice, PrimaryMap};
|
||||
use cranelift_wasm::DefinedFuncIndex;
|
||||
use jit_code::JITCode;
|
||||
use link::link_module;
|
||||
use resolver::Resolver;
|
||||
use std::cmp::max;
|
||||
use std::rc::Rc;
|
||||
use std::slice;
|
||||
use std::string::String;
|
||||
use std::vec::Vec;
|
||||
use std::{mem, ptr};
|
||||
use trampoline_park::TrampolinePark;
|
||||
use wasmtime_environ::{
|
||||
compile_module, Compilation, CompileError, DataInitializer, Module, ModuleEnvironment, Tunables,
|
||||
};
|
||||
use wasmtime_runtime::{
|
||||
wasmtime_call_trampoline, Export, Imports, Instance, InstantiationError, VMFunctionBody,
|
||||
};
|
||||
|
||||
/// `InstancePlus` holds an `Instance` and adds support for performing actions
|
||||
/// such as the "invoke" command in wast.
|
||||
///
|
||||
/// TODO: Think of a better name.
|
||||
#[derive(Debug)]
|
||||
pub struct InstancePlus {
|
||||
/// The contained instance.
|
||||
pub instance: Box<Instance>,
|
||||
|
||||
/// Trampolines for calling into JIT code.
|
||||
trampolines: TrampolinePark,
|
||||
}
|
||||
|
||||
impl InstancePlus {
|
||||
/// Create a new `InstancePlus` by compiling the wasm module in `data` and instatiating it.
|
||||
pub fn new(
|
||||
jit_code: &mut JITCode,
|
||||
isa: &isa::TargetIsa,
|
||||
data: &[u8],
|
||||
resolver: &mut Resolver,
|
||||
) -> Result<Self, ActionError> {
|
||||
let mut module = Module::new();
|
||||
|
||||
// TODO: Allow the tunables to be overridden.
|
||||
let tunables = Tunables::default();
|
||||
|
||||
let (lazy_function_body_inputs, lazy_data_initializers) = {
|
||||
let environ = ModuleEnvironment::new(isa, &mut module, tunables);
|
||||
|
||||
let translation = environ
|
||||
.translate(&data)
|
||||
.map_err(|error| ActionError::Compile(CompileError::Wasm(error)))?;
|
||||
|
||||
(
|
||||
translation.lazy.function_body_inputs,
|
||||
translation.lazy.data_initializers,
|
||||
)
|
||||
};
|
||||
|
||||
let (compilation, relocations) = compile_module(&module, &lazy_function_body_inputs, isa)
|
||||
.map_err(ActionError::Compile)?;
|
||||
|
||||
let allocated_functions = allocate_functions(jit_code, compilation).map_err(|message| {
|
||||
ActionError::Instantiate(InstantiationError::Resource(format!(
|
||||
"failed to allocate memory for functions: {}",
|
||||
message
|
||||
)))
|
||||
})?;
|
||||
|
||||
let imports = link_module(&module, &allocated_functions, relocations, resolver)
|
||||
.map_err(ActionError::Link)?;
|
||||
|
||||
// Gather up the pointers to the compiled functions.
|
||||
let finished_functions: BoxedSlice<DefinedFuncIndex, *const VMFunctionBody> =
|
||||
allocated_functions
|
||||
.into_iter()
|
||||
.map(|(_index, allocated)| {
|
||||
let fatptr: *const [VMFunctionBody] = *allocated;
|
||||
fatptr as *const VMFunctionBody
|
||||
})
|
||||
.collect::<PrimaryMap<_, _>>()
|
||||
.into_boxed_slice();
|
||||
|
||||
// Make all code compiled thus far executable.
|
||||
jit_code.publish();
|
||||
|
||||
Self::with_parts(
|
||||
Rc::new(module),
|
||||
finished_functions,
|
||||
imports,
|
||||
lazy_data_initializers,
|
||||
)
|
||||
}
|
||||
|
||||
/// Construct a new `InstancePlus` from the parts needed to produce an `Instance`.
|
||||
pub fn with_parts(
|
||||
module: Rc<Module>,
|
||||
finished_functions: BoxedSlice<DefinedFuncIndex, *const VMFunctionBody>,
|
||||
imports: Imports,
|
||||
data_initializers: Vec<DataInitializer>,
|
||||
) -> Result<Self, ActionError> {
|
||||
let instance = Instance::new(module, finished_functions, imports, data_initializers)
|
||||
.map_err(ActionError::Instantiate)?;
|
||||
|
||||
Ok(Self::with_instance(instance))
|
||||
}
|
||||
|
||||
/// Construct a new `InstancePlus` from an existing instance.
|
||||
pub fn with_instance(instance: Box<Instance>) -> Self {
|
||||
Self {
|
||||
instance,
|
||||
trampolines: TrampolinePark::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Invoke a function in this `Instance` identified by an export name.
|
||||
pub fn invoke(
|
||||
&mut self,
|
||||
jit_code: &mut JITCode,
|
||||
isa: &isa::TargetIsa,
|
||||
function_name: &str,
|
||||
args: &[RuntimeValue],
|
||||
) -> Result<ActionOutcome, ActionError> {
|
||||
let (address, signature, callee_vmctx) = match self.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() {
|
||||
assert_eq!(value.value_type(), signature.params[index].value_type);
|
||||
}
|
||||
|
||||
// TODO: Support values larger than u64.
|
||||
let mut values_vec: Vec<u64> = Vec::new();
|
||||
let value_size = mem::size_of::<u64>();
|
||||
values_vec.resize(max(signature.params.len(), signature.returns.len()), 0u64);
|
||||
|
||||
// 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 = self
|
||||
.trampolines
|
||||
.get(jit_code, isa, address, &signature, value_size)?;
|
||||
|
||||
// Make all JIT code produced thus far executable.
|
||||
jit_code.publish();
|
||||
|
||||
// Call the trampoline.
|
||||
if let Err(message) = unsafe {
|
||||
wasmtime_call_trampoline(
|
||||
exec_code_buf,
|
||||
values_vec.as_mut_ptr() as *mut u8,
|
||||
callee_vmctx,
|
||||
)
|
||||
} {
|
||||
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(
|
||||
&self,
|
||||
memory_name: &str,
|
||||
start: usize,
|
||||
len: usize,
|
||||
) -> Result<&[u8], ActionError> {
|
||||
let address = match unsafe { self.instance.lookup_immutable(memory_name) } {
|
||||
Some(Export::Memory {
|
||||
address,
|
||||
memory: _memory,
|
||||
vmctx: _vmctx,
|
||||
}) => address,
|
||||
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 = &*address;
|
||||
&slice::from_raw_parts(memory_def.base, memory_def.current_length)[start..start + len]
|
||||
})
|
||||
}
|
||||
|
||||
/// Read a global in this `Instance` identified by an export name.
|
||||
pub fn get(&self, global_name: &str) -> Result<RuntimeValue, ActionError> {
|
||||
let (address, global) = match unsafe { self.instance.lookup_immutable(global_name) } {
|
||||
Some(Export::Global { address, global }) => (address, 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 = &*address;
|
||||
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
|
||||
)))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn allocate_functions(
|
||||
jit_code: &mut JITCode,
|
||||
compilation: Compilation,
|
||||
) -> Result<PrimaryMap<DefinedFuncIndex, *mut [VMFunctionBody]>, String> {
|
||||
let mut result = PrimaryMap::with_capacity(compilation.functions.len());
|
||||
for (_, body) in compilation.functions.into_iter() {
|
||||
let fatptr: *mut [VMFunctionBody] = jit_code.allocate_copy_of_byte_slice(body)?;
|
||||
result.push(fatptr);
|
||||
}
|
||||
Ok(result)
|
||||
}
|
||||
@@ -7,15 +7,15 @@ use std::{cmp, mem};
|
||||
use wasmtime_runtime::{Mmap, VMFunctionBody};
|
||||
|
||||
/// Memory manager for executable code.
|
||||
pub struct Code {
|
||||
pub struct JITCode {
|
||||
current: Mmap,
|
||||
mmaps: Vec<Mmap>,
|
||||
position: usize,
|
||||
published: usize,
|
||||
}
|
||||
|
||||
impl Code {
|
||||
/// Create a new `Code` instance.
|
||||
impl JITCode {
|
||||
/// Create a new `JITCode` instance.
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
current: Mmap::new(),
|
||||
@@ -39,16 +39,17 @@ extern crate failure;
|
||||
extern crate failure_derive;
|
||||
|
||||
mod action;
|
||||
mod code;
|
||||
mod export;
|
||||
mod instance_plus;
|
||||
mod jit_code;
|
||||
mod link;
|
||||
mod world;
|
||||
mod resolver;
|
||||
mod trampoline_park;
|
||||
|
||||
pub use action::{ActionError, ActionOutcome, RuntimeValue};
|
||||
pub use code::Code;
|
||||
pub use export::{Export, NullResolver, Resolver};
|
||||
pub use instance_plus::InstancePlus;
|
||||
pub use jit_code::JITCode;
|
||||
pub use link::link_module;
|
||||
pub use world::InstanceWorld;
|
||||
pub use resolver::{NullResolver, Resolver};
|
||||
|
||||
#[cfg(not(feature = "std"))]
|
||||
mod std {
|
||||
|
||||
@@ -1,16 +1,18 @@
|
||||
use cranelift_codegen::binemit::Reloc;
|
||||
use cranelift_entity::PrimaryMap;
|
||||
use cranelift_wasm::{DefinedFuncIndex, Global, GlobalInit, Memory, Table, TableElementType};
|
||||
use export::{Export, FunctionExport, Resolver};
|
||||
use resolver::Resolver;
|
||||
use std::ptr::write_unaligned;
|
||||
use std::string::String;
|
||||
use std::vec::Vec;
|
||||
use wasmtime_environ::{
|
||||
MemoryPlan, MemoryStyle, Module, Relocation, RelocationTarget, Relocations, TablePlan,
|
||||
TableStyle,
|
||||
};
|
||||
use wasmtime_runtime::libcalls;
|
||||
use wasmtime_runtime::{Imports, VMFunctionBody, VMGlobalImport, VMMemoryImport, VMTableImport};
|
||||
use wasmtime_runtime::{
|
||||
Export, Imports, VMFunctionBody, VMFunctionImport, VMGlobalImport, VMMemoryImport,
|
||||
VMTableImport,
|
||||
};
|
||||
|
||||
/// A link error, such as incompatible or unmatched imports/exports.
|
||||
#[derive(Fail, Debug)]
|
||||
@@ -28,7 +30,11 @@ pub fn link_module(
|
||||
for (index, (ref module_name, ref field)) in module.imported_funcs.iter() {
|
||||
match resolver.resolve(module_name, field) {
|
||||
Some(export_value) => match export_value {
|
||||
Export::Function(FunctionExport { address, signature }) => {
|
||||
Export::Function {
|
||||
address,
|
||||
signature,
|
||||
vmctx,
|
||||
} => {
|
||||
let import_signature = &module.signatures[module.functions[index]];
|
||||
if signature != *import_signature {
|
||||
// TODO: If the difference is in the calling convention,
|
||||
@@ -39,7 +45,10 @@ pub fn link_module(
|
||||
signature, import_signature)
|
||||
));
|
||||
}
|
||||
function_imports.push(address);
|
||||
function_imports.push(VMFunctionImport {
|
||||
body: address,
|
||||
vmctx,
|
||||
});
|
||||
}
|
||||
Export::Table { .. } | Export::Memory { .. } | Export::Global { .. } => {
|
||||
return Err(LinkError(format!(
|
||||
@@ -104,12 +113,28 @@ pub fn link_module(
|
||||
memory,
|
||||
} => {
|
||||
let import_memory = &module.memory_plans[index];
|
||||
if is_memory_compatible(&memory, import_memory) {
|
||||
if !is_memory_compatible(&memory, import_memory) {
|
||||
return Err(LinkError(format!(
|
||||
"{}/{}: exported memory incompatible with memory import",
|
||||
module_name, field
|
||||
)));
|
||||
}
|
||||
|
||||
// Sanity-check: Ensure that the imported memory has at least
|
||||
// guard-page protections the importing module expects it to have.
|
||||
match (memory.style, &import_memory.style) {
|
||||
(
|
||||
MemoryStyle::Static { bound },
|
||||
MemoryStyle::Static {
|
||||
bound: import_bound,
|
||||
},
|
||||
) => {
|
||||
assert!(bound >= *import_bound);
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
assert!(memory.offset_guard_size >= import_memory.offset_guard_size);
|
||||
|
||||
memory_imports.push(VMMemoryImport {
|
||||
from: address,
|
||||
vmctx,
|
||||
@@ -161,17 +186,15 @@ pub fn link_module(
|
||||
}
|
||||
}
|
||||
|
||||
let imports = Imports::new(
|
||||
// Apply relocations, now that we have virtual addresses for everything.
|
||||
relocate(allocated_functions, relocations, &module);
|
||||
|
||||
Ok(Imports::new(
|
||||
function_imports,
|
||||
table_imports,
|
||||
memory_imports,
|
||||
global_imports,
|
||||
);
|
||||
|
||||
// Apply relocations, now that we have virtual addresses for everything.
|
||||
relocate(&imports, allocated_functions, relocations, &module);
|
||||
|
||||
Ok(imports)
|
||||
))
|
||||
}
|
||||
|
||||
fn is_global_compatible(exported: &Global, imported: &Global) -> bool {
|
||||
@@ -193,14 +216,6 @@ fn is_global_compatible(exported: &Global, imported: &Global) -> bool {
|
||||
exported_ty == imported_ty && imported_mutability == exported_mutability
|
||||
}
|
||||
|
||||
fn is_table_style_compatible(exported_style: &TableStyle, imported_style: &TableStyle) -> bool {
|
||||
match exported_style {
|
||||
TableStyle::CallerChecksSignature => match imported_style {
|
||||
TableStyle::CallerChecksSignature => true,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn is_table_element_type_compatible(
|
||||
exported_type: TableElementType,
|
||||
imported_type: TableElementType,
|
||||
@@ -225,7 +240,7 @@ fn is_table_compatible(exported: &TablePlan, imported: &TablePlan) -> bool {
|
||||
minimum: exported_minimum,
|
||||
maximum: exported_maximum,
|
||||
},
|
||||
style: exported_style,
|
||||
style: _exported_style,
|
||||
} = exported;
|
||||
let TablePlan {
|
||||
table:
|
||||
@@ -234,30 +249,14 @@ fn is_table_compatible(exported: &TablePlan, imported: &TablePlan) -> bool {
|
||||
minimum: imported_minimum,
|
||||
maximum: imported_maximum,
|
||||
},
|
||||
style: imported_style,
|
||||
style: _imported_style,
|
||||
} = imported;
|
||||
|
||||
is_table_element_type_compatible(*exported_ty, *imported_ty)
|
||||
&& imported_minimum >= exported_minimum
|
||||
&& imported_maximum <= exported_maximum
|
||||
&& is_table_style_compatible(imported_style, exported_style)
|
||||
}
|
||||
|
||||
fn is_memory_style_compatible(exported_style: &MemoryStyle, imported_style: &MemoryStyle) -> bool {
|
||||
match exported_style {
|
||||
MemoryStyle::Dynamic => match imported_style {
|
||||
MemoryStyle::Dynamic => true,
|
||||
_ => false,
|
||||
},
|
||||
MemoryStyle::Static {
|
||||
bound: imported_bound,
|
||||
} => match imported_style {
|
||||
MemoryStyle::Static {
|
||||
bound: exported_bound,
|
||||
} => exported_bound >= imported_bound,
|
||||
_ => false,
|
||||
},
|
||||
}
|
||||
&& imported_minimum <= exported_minimum
|
||||
&& (imported_maximum.is_none()
|
||||
|| (!exported_maximum.is_none()
|
||||
&& imported_maximum.unwrap() >= exported_maximum.unwrap()))
|
||||
}
|
||||
|
||||
fn is_memory_compatible(exported: &MemoryPlan, imported: &MemoryPlan) -> bool {
|
||||
@@ -268,8 +267,8 @@ fn is_memory_compatible(exported: &MemoryPlan, imported: &MemoryPlan) -> bool {
|
||||
maximum: exported_maximum,
|
||||
shared: exported_shared,
|
||||
},
|
||||
style: exported_style,
|
||||
offset_guard_size: exported_offset_guard_size,
|
||||
style: _exported_style,
|
||||
offset_guard_size: _exported_offset_guard_size,
|
||||
} = exported;
|
||||
let MemoryPlan {
|
||||
memory:
|
||||
@@ -278,20 +277,19 @@ fn is_memory_compatible(exported: &MemoryPlan, imported: &MemoryPlan) -> bool {
|
||||
maximum: imported_maximum,
|
||||
shared: imported_shared,
|
||||
},
|
||||
style: imported_style,
|
||||
offset_guard_size: imported_offset_guard_size,
|
||||
style: _imported_style,
|
||||
offset_guard_size: _imported_offset_guard_size,
|
||||
} = imported;
|
||||
|
||||
imported_minimum >= exported_minimum
|
||||
&& imported_maximum <= exported_maximum
|
||||
imported_minimum <= exported_minimum
|
||||
&& (imported_maximum.is_none()
|
||||
|| (!exported_maximum.is_none()
|
||||
&& imported_maximum.unwrap() >= exported_maximum.unwrap()))
|
||||
&& exported_shared == imported_shared
|
||||
&& is_memory_style_compatible(exported_style, imported_style)
|
||||
&& exported_offset_guard_size >= imported_offset_guard_size
|
||||
}
|
||||
|
||||
/// Performs the relocations inside the function bytecode, provided the necessary metadata.
|
||||
fn relocate(
|
||||
imports: &Imports,
|
||||
allocated_functions: &PrimaryMap<DefinedFuncIndex, *mut [VMFunctionBody]>,
|
||||
relocations: PrimaryMap<DefinedFuncIndex, Vec<Relocation>>,
|
||||
module: &Module,
|
||||
@@ -305,7 +303,7 @@ fn relocate(
|
||||
let fatptr: *const [VMFunctionBody] = allocated_functions[f];
|
||||
fatptr as *const VMFunctionBody as usize
|
||||
}
|
||||
None => imports.functions[index] as usize,
|
||||
None => panic!("direct call to import"),
|
||||
},
|
||||
RelocationTarget::Memory32Grow => wasmtime_memory32_grow as usize,
|
||||
RelocationTarget::Memory32Size => wasmtime_memory32_size as usize,
|
||||
|
||||
16
lib/execute/src/resolver.rs
Normal file
16
lib/execute/src/resolver.rs
Normal file
@@ -0,0 +1,16 @@
|
||||
use wasmtime_runtime::Export;
|
||||
|
||||
/// Import resolver connects imports with available exported values.
|
||||
pub trait Resolver {
|
||||
/// Resolve the given module/field combo.
|
||||
fn resolve(&mut self, module: &str, field: &str) -> Option<Export>;
|
||||
}
|
||||
|
||||
/// `Resolver` implementation that always resolves to `None`.
|
||||
pub struct NullResolver {}
|
||||
|
||||
impl Resolver for NullResolver {
|
||||
fn resolve(&mut self, _module: &str, _field: &str) -> Option<Export> {
|
||||
None
|
||||
}
|
||||
}
|
||||
152
lib/execute/src/trampoline_park.rs
Normal file
152
lib/execute/src/trampoline_park.rs
Normal file
@@ -0,0 +1,152 @@
|
||||
use action::ActionError;
|
||||
use cranelift_codegen::ir::InstBuilder;
|
||||
use cranelift_codegen::Context;
|
||||
use cranelift_codegen::{binemit, ir, isa};
|
||||
use cranelift_frontend::{FunctionBuilder, FunctionBuilderContext};
|
||||
use jit_code::JITCode;
|
||||
use std::collections::HashMap;
|
||||
use std::fmt;
|
||||
use wasmtime_environ::{CompileError, RelocSink};
|
||||
use wasmtime_runtime::{InstantiationError, VMFunctionBody};
|
||||
|
||||
pub struct TrampolinePark {
|
||||
/// Memoized per-function trampolines.
|
||||
memoized: HashMap<*const VMFunctionBody, *const VMFunctionBody>,
|
||||
|
||||
/// The `FunctionBuilderContext`, shared between function compilations.
|
||||
fn_builder_ctx: FunctionBuilderContext,
|
||||
}
|
||||
|
||||
impl TrampolinePark {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
memoized: HashMap::new(),
|
||||
fn_builder_ctx: FunctionBuilderContext::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get(
|
||||
&mut self,
|
||||
jit_code: &mut JITCode,
|
||||
isa: &isa::TargetIsa,
|
||||
callee_address: *const VMFunctionBody,
|
||||
signature: &ir::Signature,
|
||||
value_size: usize,
|
||||
) -> Result<*const VMFunctionBody, ActionError> {
|
||||
use std::collections::hash_map::Entry::{Occupied, Vacant};
|
||||
Ok(match self.memoized.entry(callee_address) {
|
||||
Occupied(entry) => *entry.get(),
|
||||
Vacant(entry) => {
|
||||
let body = make_trampoline(
|
||||
&mut self.fn_builder_ctx,
|
||||
jit_code,
|
||||
isa,
|
||||
callee_address,
|
||||
signature,
|
||||
value_size,
|
||||
)?;
|
||||
entry.insert(body);
|
||||
body
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for TrampolinePark {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
// The `fn_builder_ctx` field is just a cache and has no logical state.
|
||||
write!(f, "{:?}", self.memoized)
|
||||
}
|
||||
}
|
||||
|
||||
fn make_trampoline(
|
||||
fn_builder_ctx: &mut FunctionBuilderContext,
|
||||
jit_code: &mut JITCode,
|
||||
isa: &isa::TargetIsa,
|
||||
callee_address: *const VMFunctionBody,
|
||||
signature: &ir::Signature,
|
||||
value_size: usize,
|
||||
) -> Result<*const VMFunctionBody, ActionError> {
|
||||
let pointer_type = isa.pointer_type();
|
||||
let mut wrapper_sig = ir::Signature::new(isa.frontend_config().default_call_conv);
|
||||
|
||||
// Add the `values_vec` parameter.
|
||||
wrapper_sig.params.push(ir::AbiParam::new(pointer_type));
|
||||
// Add the `vmctx` parameter.
|
||||
wrapper_sig.params.push(ir::AbiParam::special(
|
||||
pointer_type,
|
||||
ir::ArgumentPurpose::VMContext,
|
||||
));
|
||||
|
||||
let mut context = Context::new();
|
||||
context.func = ir::Function::with_name_signature(ir::ExternalName::user(0, 0), wrapper_sig);
|
||||
|
||||
{
|
||||
let mut builder = FunctionBuilder::new(&mut context.func, fn_builder_ctx);
|
||||
let block0 = builder.create_ebb();
|
||||
|
||||
builder.append_ebb_params_for_function_params(block0);
|
||||
builder.switch_to_block(block0);
|
||||
builder.seal_block(block0);
|
||||
|
||||
let mut callee_args = Vec::new();
|
||||
let pointer_type = isa.pointer_type();
|
||||
|
||||
let (values_vec_ptr_val, vmctx_ptr_val) = {
|
||||
let params = builder.func.dfg.ebb_params(block0);
|
||||
(params[0], params[1])
|
||||
};
|
||||
|
||||
// Load the argument values out of `values_vec`.
|
||||
let mflags = ir::MemFlags::trusted();
|
||||
for (i, r) in signature.params.iter().enumerate() {
|
||||
let value = match r.purpose {
|
||||
ir::ArgumentPurpose::Normal => builder.ins().load(
|
||||
r.value_type,
|
||||
mflags,
|
||||
values_vec_ptr_val,
|
||||
(i * value_size) as i32,
|
||||
),
|
||||
ir::ArgumentPurpose::VMContext => vmctx_ptr_val,
|
||||
other => panic!("unsupported argument purpose {}", other),
|
||||
};
|
||||
callee_args.push(value);
|
||||
}
|
||||
|
||||
let new_sig = builder.import_signature(signature.clone());
|
||||
|
||||
// TODO: It's possible to make this a direct call. We just need Cranelift
|
||||
// to support functions declared with an immediate integer address.
|
||||
// ExternalName::Absolute(u64). Let's do it.
|
||||
let callee_value = builder.ins().iconst(pointer_type, callee_address as i64);
|
||||
let call = builder
|
||||
.ins()
|
||||
.call_indirect(new_sig, callee_value, &callee_args);
|
||||
|
||||
let results = builder.func.dfg.inst_results(call).to_vec();
|
||||
|
||||
// Store the return values into `values_vec`.
|
||||
let mflags = ir::MemFlags::trusted();
|
||||
for (i, r) in results.iter().enumerate() {
|
||||
builder
|
||||
.ins()
|
||||
.store(mflags, *r, values_vec_ptr_val, (i * value_size) as i32);
|
||||
}
|
||||
|
||||
builder.ins().return_(&[]);
|
||||
builder.finalize()
|
||||
}
|
||||
|
||||
let mut code_buf: Vec<u8> = Vec::new();
|
||||
let mut reloc_sink = RelocSink::new();
|
||||
let mut trap_sink = binemit::NullTrapSink {};
|
||||
context
|
||||
.compile_and_emit(isa, &mut code_buf, &mut reloc_sink, &mut trap_sink)
|
||||
.map_err(|error| ActionError::Compile(CompileError::Codegen(error)))?;
|
||||
assert!(reloc_sink.func_relocs.is_empty());
|
||||
|
||||
Ok(jit_code
|
||||
.allocate_copy_of_byte_slice(&code_buf)
|
||||
.map_err(|message| ActionError::Instantiate(InstantiationError::Resource(message)))?
|
||||
.as_ptr())
|
||||
}
|
||||
@@ -1,553 +0,0 @@
|
||||
use action::{ActionError, ActionOutcome, RuntimeValue};
|
||||
use code::Code;
|
||||
use cranelift_codegen::ir::InstBuilder;
|
||||
use cranelift_codegen::Context;
|
||||
use cranelift_codegen::{binemit, ir, isa};
|
||||
use cranelift_entity::{BoxedSlice, EntityRef, PrimaryMap};
|
||||
use cranelift_frontend::{FunctionBuilder, FunctionBuilderContext};
|
||||
use cranelift_wasm::{
|
||||
DefinedFuncIndex, DefinedGlobalIndex, DefinedMemoryIndex, DefinedTableIndex, FuncIndex,
|
||||
GlobalIndex, MemoryIndex, TableIndex,
|
||||
};
|
||||
use export::Resolver;
|
||||
use link::link_module;
|
||||
use std::cmp::max;
|
||||
use std::collections::HashMap;
|
||||
use std::slice;
|
||||
use std::string::String;
|
||||
use std::vec::Vec;
|
||||
use std::{mem, ptr};
|
||||
use wasmtime_environ::{
|
||||
compile_module, Compilation, CompileError, Export, Module, ModuleEnvironment, RelocSink,
|
||||
Tunables,
|
||||
};
|
||||
use wasmtime_runtime::{
|
||||
wasmtime_call_trampoline, wasmtime_init_eager, wasmtime_init_finish, Instance, VMContext,
|
||||
VMFunctionBody, VMGlobalDefinition, VMGlobalImport, VMMemoryDefinition, VMMemoryImport,
|
||||
VMTableDefinition, VMTableImport,
|
||||
};
|
||||
|
||||
/// A module, an instance of that module, and accompanying compilation artifacts.
|
||||
///
|
||||
/// TODO: Rename and reorganize this.
|
||||
pub struct InstanceWorld {
|
||||
module: Module,
|
||||
instance: Instance,
|
||||
|
||||
/// Pointers to functions in executable memory.
|
||||
finished_functions: BoxedSlice<DefinedFuncIndex, *const VMFunctionBody>,
|
||||
|
||||
/// Trampolines for calling into JIT code.
|
||||
trampolines: TrampolinePark,
|
||||
}
|
||||
|
||||
impl InstanceWorld {
|
||||
/// Create a new `InstanceWorld` by compiling the wasm module in `data` and instatiating it.
|
||||
///
|
||||
/// `finished_functions` holds the function bodies
|
||||
/// which have been placed in executable memory and linked.
|
||||
pub fn new(
|
||||
code: &mut Code,
|
||||
isa: &isa::TargetIsa,
|
||||
data: &[u8],
|
||||
resolver: &mut Resolver,
|
||||
) -> Result<Self, ActionError> {
|
||||
let mut module = Module::new();
|
||||
// TODO: Allow the tunables to be overridden.
|
||||
let tunables = Tunables::default();
|
||||
let (lazy_function_body_inputs, lazy_data_initializers) = {
|
||||
let environ = ModuleEnvironment::new(isa, &mut module, tunables);
|
||||
|
||||
let translation = environ
|
||||
.translate(&data)
|
||||
.map_err(|error| ActionError::Compile(CompileError::Wasm(error)))?;
|
||||
|
||||
(
|
||||
translation.lazy.function_body_inputs,
|
||||
translation.lazy.data_initializers,
|
||||
)
|
||||
};
|
||||
|
||||
let (compilation, relocations) = compile_module(&module, &lazy_function_body_inputs, isa)
|
||||
.map_err(ActionError::Compile)?;
|
||||
|
||||
let allocated_functions =
|
||||
allocate_functions(code, compilation).map_err(ActionError::Resource)?;
|
||||
|
||||
let imports = link_module(&module, &allocated_functions, relocations, resolver)
|
||||
.map_err(ActionError::Link)?;
|
||||
|
||||
let finished_functions: BoxedSlice<DefinedFuncIndex, *const VMFunctionBody> =
|
||||
allocated_functions
|
||||
.into_iter()
|
||||
.map(|(_index, allocated)| {
|
||||
let fatptr: *const [VMFunctionBody] = *allocated;
|
||||
fatptr as *const VMFunctionBody
|
||||
})
|
||||
.collect::<PrimaryMap<_, _>>()
|
||||
.into_boxed_slice();
|
||||
|
||||
let instance = Instance::new(
|
||||
&module,
|
||||
&finished_functions,
|
||||
imports,
|
||||
&lazy_data_initializers,
|
||||
)
|
||||
.map_err(ActionError::Resource)?;
|
||||
|
||||
let fn_builder_ctx = FunctionBuilderContext::new();
|
||||
|
||||
let mut result = Self {
|
||||
module,
|
||||
instance,
|
||||
finished_functions,
|
||||
trampolines: TrampolinePark {
|
||||
memo: HashMap::new(),
|
||||
fn_builder_ctx,
|
||||
},
|
||||
};
|
||||
|
||||
// The WebAssembly spec specifies that the start function is
|
||||
// invoked automatically at instantiation time.
|
||||
match result.invoke_start_function(code, isa)? {
|
||||
ActionOutcome::Returned { .. } => {}
|
||||
ActionOutcome::Trapped { message } => {
|
||||
// Instantiation fails if the start function traps.
|
||||
return Err(ActionError::Start(message));
|
||||
}
|
||||
}
|
||||
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
fn get_imported_function(&self, index: FuncIndex) -> Option<*const VMFunctionBody> {
|
||||
if index.index() < self.module.imported_funcs.len() {
|
||||
Some(unsafe { self.instance.vmctx().imported_function(index) })
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Add an accessor for table elements.
|
||||
#[allow(dead_code)]
|
||||
fn get_imported_table(&self, index: TableIndex) -> Option<&VMTableImport> {
|
||||
if index.index() < self.module.imported_tables.len() {
|
||||
Some(unsafe { self.instance.vmctx().imported_table(index) })
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn get_imported_memory(&self, index: MemoryIndex) -> Option<&VMMemoryImport> {
|
||||
if index.index() < self.module.imported_memories.len() {
|
||||
Some(unsafe { self.instance.vmctx().imported_memory(index) })
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn get_imported_global(&self, index: GlobalIndex) -> Option<&VMGlobalImport> {
|
||||
if index.index() < self.module.imported_globals.len() {
|
||||
Some(unsafe { self.instance.vmctx().imported_global(index) })
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn get_finished_function(&self, index: DefinedFuncIndex) -> Option<*const VMFunctionBody> {
|
||||
self.finished_functions.get(index).cloned()
|
||||
}
|
||||
|
||||
// TODO: Add an accessor for table elements.
|
||||
#[allow(dead_code)]
|
||||
fn get_defined_table(&self, index: DefinedTableIndex) -> Option<&VMTableDefinition> {
|
||||
if self.module.table_index(index).index() < self.module.table_plans.len() {
|
||||
Some(unsafe { self.instance.vmctx().table(index) })
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn get_defined_memory(&self, index: DefinedMemoryIndex) -> Option<&VMMemoryDefinition> {
|
||||
if self.module.memory_index(index).index() < self.module.memory_plans.len() {
|
||||
Some(unsafe { self.instance.vmctx().memory(index) })
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn get_defined_global(&self, index: DefinedGlobalIndex) -> Option<&VMGlobalDefinition> {
|
||||
if self.module.global_index(index).index() < self.module.globals.len() {
|
||||
Some(unsafe { self.instance.vmctx().global(index) })
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// Invoke a function in this `InstanceWorld` by name.
|
||||
pub fn invoke(
|
||||
&mut self,
|
||||
code: &mut Code,
|
||||
isa: &isa::TargetIsa,
|
||||
function_name: &str,
|
||||
args: &[RuntimeValue],
|
||||
) -> Result<ActionOutcome, ActionError> {
|
||||
let fn_index = match self.module.exports.get(function_name) {
|
||||
Some(Export::Function(index)) => *index,
|
||||
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
|
||||
)))
|
||||
}
|
||||
};
|
||||
|
||||
self.invoke_by_index(code, isa, fn_index, args)
|
||||
}
|
||||
|
||||
/// Invoke the WebAssembly start function of the instance, if one is present.
|
||||
fn invoke_start_function(
|
||||
&mut self,
|
||||
code: &mut Code,
|
||||
isa: &isa::TargetIsa,
|
||||
) -> Result<ActionOutcome, ActionError> {
|
||||
if let Some(start_index) = self.module.start_func {
|
||||
self.invoke_by_index(code, isa, 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.
|
||||
fn invoke_by_index(
|
||||
&mut self,
|
||||
code: &mut Code,
|
||||
isa: &isa::TargetIsa,
|
||||
fn_index: FuncIndex,
|
||||
args: &[RuntimeValue],
|
||||
) -> Result<ActionOutcome, ActionError> {
|
||||
let callee_address = match self.module.defined_func_index(fn_index) {
|
||||
Some(def_fn_index) => self
|
||||
.get_finished_function(def_fn_index)
|
||||
.ok_or_else(|| ActionError::Index(def_fn_index.index() as u64))?,
|
||||
None => self
|
||||
.get_imported_function(fn_index)
|
||||
.ok_or_else(|| ActionError::Index(fn_index.index() as u64))?,
|
||||
};
|
||||
|
||||
// Rather than writing inline assembly to jump to the code region, we use the fact that
|
||||
// the Rust ABI for calling a function with no arguments and no return values matches the one
|
||||
// of the generated code. Thanks to this, we can transmute the code region into a first-class
|
||||
// Rust function and call it.
|
||||
// Ensure that our signal handlers are ready for action.
|
||||
wasmtime_init_eager();
|
||||
wasmtime_init_finish(self.instance.vmctx_mut());
|
||||
|
||||
let signature = &self.module.signatures[self.module.functions[fn_index]];
|
||||
let vmctx: *mut VMContext = self.instance.vmctx_mut();
|
||||
|
||||
for (index, value) in args.iter().enumerate() {
|
||||
assert_eq!(value.value_type(), signature.params[index].value_type);
|
||||
}
|
||||
|
||||
// TODO: Support values larger than u64.
|
||||
let mut values_vec: Vec<u64> = Vec::new();
|
||||
let value_size = mem::size_of::<u64>();
|
||||
values_vec.resize(max(signature.params.len(), signature.returns.len()), 0u64);
|
||||
|
||||
// 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),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Store the vmctx value into `values_vec`.
|
||||
unsafe {
|
||||
let ptr = values_vec.as_mut_ptr().add(args.len());
|
||||
ptr::write(ptr as *mut usize, vmctx as usize)
|
||||
}
|
||||
|
||||
// Get the trampoline to call for this function.
|
||||
let exec_code_buf =
|
||||
self.trampolines
|
||||
.get(code, isa, callee_address, &signature, value_size)?;
|
||||
|
||||
// Make all JIT code produced thus far executable.
|
||||
code.publish();
|
||||
|
||||
// Call the trampoline.
|
||||
if let Err(message) = unsafe {
|
||||
wasmtime_call_trampoline(
|
||||
exec_code_buf,
|
||||
values_vec.as_mut_ptr() as *mut u8,
|
||||
self.instance.vmctx_mut(),
|
||||
)
|
||||
} {
|
||||
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 })
|
||||
}
|
||||
|
||||
/// Read a global in this `InstanceWorld` by name.
|
||||
pub fn get(&self, global_name: &str) -> Result<RuntimeValue, ActionError> {
|
||||
let global_index = match self.module.exports.get(global_name) {
|
||||
Some(Export::Global(index)) => *index,
|
||||
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
|
||||
)))
|
||||
}
|
||||
};
|
||||
|
||||
self.get_by_index(global_index)
|
||||
}
|
||||
|
||||
/// Reads the value of the indexed global variable in `module`.
|
||||
pub fn get_by_index(&self, global_index: GlobalIndex) -> Result<RuntimeValue, ActionError> {
|
||||
let global_address = match self.module.defined_global_index(global_index) {
|
||||
Some(def_global_index) => self
|
||||
.get_defined_global(def_global_index)
|
||||
.ok_or_else(|| ActionError::Index(def_global_index.index() as u64))?,
|
||||
None => {
|
||||
let from: *const VMGlobalDefinition = self
|
||||
.get_imported_global(global_index)
|
||||
.ok_or_else(|| ActionError::Index(global_index.index() as u64))?
|
||||
.from;
|
||||
from
|
||||
}
|
||||
};
|
||||
let global_def = unsafe { &*global_address };
|
||||
|
||||
unsafe {
|
||||
Ok(
|
||||
match self
|
||||
.module
|
||||
.globals
|
||||
.get(global_index)
|
||||
.ok_or_else(|| ActionError::Index(global_index.index() as u64))?
|
||||
.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
|
||||
)))
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a slice of the contents of allocated linear memory.
|
||||
pub fn inspect_memory(
|
||||
&self,
|
||||
memory_index: MemoryIndex,
|
||||
address: usize,
|
||||
len: usize,
|
||||
) -> Result<&[u8], ActionError> {
|
||||
let memory_address = match self.module.defined_memory_index(memory_index) {
|
||||
Some(def_memory_index) => self
|
||||
.get_defined_memory(def_memory_index)
|
||||
.ok_or_else(|| ActionError::Index(def_memory_index.index() as u64))?,
|
||||
None => {
|
||||
let from: *const VMMemoryDefinition = self
|
||||
.get_imported_memory(memory_index)
|
||||
.ok_or_else(|| ActionError::Index(memory_index.index() as u64))?
|
||||
.from;
|
||||
from
|
||||
}
|
||||
};
|
||||
let memory_def = unsafe { &*memory_address };
|
||||
|
||||
Ok(unsafe {
|
||||
&slice::from_raw_parts(memory_def.base, memory_def.current_length)
|
||||
[address..address + len]
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn allocate_functions(
|
||||
code: &mut Code,
|
||||
compilation: Compilation,
|
||||
) -> Result<PrimaryMap<DefinedFuncIndex, *mut [VMFunctionBody]>, String> {
|
||||
let mut result = PrimaryMap::with_capacity(compilation.functions.len());
|
||||
for (_, body) in compilation.functions.into_iter() {
|
||||
let fatptr: *mut [VMFunctionBody] = code.allocate_copy_of_byte_slice(body)?;
|
||||
result.push(fatptr);
|
||||
}
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
struct TrampolinePark {
|
||||
/// Memorized per-function trampolines.
|
||||
memo: HashMap<*const VMFunctionBody, *const VMFunctionBody>,
|
||||
|
||||
/// The `FunctionBuilderContext`, shared between function compilations.
|
||||
fn_builder_ctx: FunctionBuilderContext,
|
||||
}
|
||||
|
||||
impl TrampolinePark {
|
||||
fn get(
|
||||
&mut self,
|
||||
code: &mut Code,
|
||||
isa: &isa::TargetIsa,
|
||||
callee_address: *const VMFunctionBody,
|
||||
signature: &ir::Signature,
|
||||
value_size: usize,
|
||||
) -> Result<*const VMFunctionBody, ActionError> {
|
||||
use std::collections::hash_map::Entry::{Occupied, Vacant};
|
||||
Ok(match self.memo.entry(callee_address) {
|
||||
Occupied(entry) => *entry.get(),
|
||||
Vacant(entry) => {
|
||||
let body = make_trampoline(
|
||||
&mut self.fn_builder_ctx,
|
||||
code,
|
||||
isa,
|
||||
callee_address,
|
||||
signature,
|
||||
value_size,
|
||||
)?;
|
||||
entry.insert(body);
|
||||
body
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn make_trampoline(
|
||||
fn_builder_ctx: &mut FunctionBuilderContext,
|
||||
code: &mut Code,
|
||||
isa: &isa::TargetIsa,
|
||||
callee_address: *const VMFunctionBody,
|
||||
signature: &ir::Signature,
|
||||
value_size: usize,
|
||||
) -> Result<*const VMFunctionBody, ActionError> {
|
||||
let pointer_type = isa.pointer_type();
|
||||
let mut wrapper_sig = ir::Signature::new(isa.frontend_config().default_call_conv);
|
||||
|
||||
// Add the `values_vec` parameter.
|
||||
wrapper_sig.params.push(ir::AbiParam::new(pointer_type));
|
||||
// Add the `vmctx` parameter.
|
||||
wrapper_sig.params.push(ir::AbiParam::special(
|
||||
pointer_type,
|
||||
ir::ArgumentPurpose::VMContext,
|
||||
));
|
||||
|
||||
let mut context = Context::new();
|
||||
context.func = ir::Function::with_name_signature(ir::ExternalName::user(0, 0), wrapper_sig);
|
||||
|
||||
{
|
||||
let mut builder = FunctionBuilder::new(&mut context.func, fn_builder_ctx);
|
||||
let block0 = builder.create_ebb();
|
||||
|
||||
builder.append_ebb_params_for_function_params(block0);
|
||||
builder.switch_to_block(block0);
|
||||
builder.seal_block(block0);
|
||||
|
||||
let mut callee_args = Vec::new();
|
||||
let pointer_type = isa.pointer_type();
|
||||
|
||||
let (values_vec_ptr_val, vmctx_ptr_val) = {
|
||||
let params = builder.func.dfg.ebb_params(block0);
|
||||
(params[0], params[1])
|
||||
};
|
||||
|
||||
// Load the argument values out of `values_vec`.
|
||||
let mflags = ir::MemFlags::trusted();
|
||||
for (i, r) in signature.params.iter().enumerate() {
|
||||
let value = match r.purpose {
|
||||
ir::ArgumentPurpose::Normal => builder.ins().load(
|
||||
r.value_type,
|
||||
mflags,
|
||||
values_vec_ptr_val,
|
||||
(i * value_size) as i32,
|
||||
),
|
||||
ir::ArgumentPurpose::VMContext => vmctx_ptr_val,
|
||||
other => panic!("unsupported argument purpose {}", other),
|
||||
};
|
||||
callee_args.push(value);
|
||||
}
|
||||
|
||||
let new_sig = builder.import_signature(signature.clone());
|
||||
|
||||
// TODO: It's possible to make this a direct call. We just need Cranelift
|
||||
// to support functions declared with an immediate integer address.
|
||||
// ExternalName::Absolute(u64). Let's do it.
|
||||
let callee_value = builder.ins().iconst(pointer_type, callee_address as i64);
|
||||
let call = builder
|
||||
.ins()
|
||||
.call_indirect(new_sig, callee_value, &callee_args);
|
||||
|
||||
let results = builder.func.dfg.inst_results(call).to_vec();
|
||||
|
||||
// Store the return values into `values_vec`.
|
||||
let mflags = ir::MemFlags::trusted();
|
||||
for (i, r) in results.iter().enumerate() {
|
||||
builder
|
||||
.ins()
|
||||
.store(mflags, *r, values_vec_ptr_val, (i * value_size) as i32);
|
||||
}
|
||||
|
||||
builder.ins().return_(&[]);
|
||||
builder.finalize()
|
||||
}
|
||||
|
||||
let mut code_buf: Vec<u8> = Vec::new();
|
||||
let mut reloc_sink = RelocSink::new();
|
||||
let mut trap_sink = binemit::NullTrapSink {};
|
||||
context
|
||||
.compile_and_emit(isa, &mut code_buf, &mut reloc_sink, &mut trap_sink)
|
||||
.map_err(|error| ActionError::Compile(CompileError::Codegen(error)))?;
|
||||
assert!(reloc_sink.func_relocs.is_empty());
|
||||
|
||||
Ok(code
|
||||
.allocate_copy_of_byte_slice(&code_buf)
|
||||
.map_err(ActionError::Resource)?
|
||||
.as_ptr())
|
||||
}
|
||||
Reference in New Issue
Block a user