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:
Dan Gohman
2018-12-11 17:12:33 -08:00
parent 6dd39dee6a
commit 3f24098edc
34 changed files with 1572 additions and 1262 deletions

2
.gitmodules vendored
View File

@@ -1,3 +1,3 @@
[submodule "lib/wast/spec_testsuite"] [submodule "lib/wast/spec_testsuite"]
path = lib/wast/spec_testsuite path = spec_testsuite
url = https://github.com/WebAssembly/testsuite url = https://github.com/WebAssembly/testsuite

View File

@@ -14,8 +14,8 @@ name = "wasmtime"
path = "src/wasmtime.rs" path = "src/wasmtime.rs"
[[bin]] [[bin]]
name = "run_wast" name = "wast"
path = "src/run_wast.rs" path = "src/wast.rs"
[[bin]] [[bin]]
name = "wasm2obj" name = "wasm2obj"

116
build.rs Normal file
View File

@@ -0,0 +1,116 @@
//! Build program to generate a program which runs all the testsuites.
//!
//! By generating a separate `#[test]` test for each file, we allow cargo test
//! to automatically run the files in parallel.
use std::env;
use std::fs::{read_dir, DirEntry, File};
use std::io::{self, Write};
use std::path::{Path, PathBuf};
fn main() {
let out_dir =
PathBuf::from(env::var("OUT_DIR").expect("The OUT_DIR environment variable must be set"));
let mut out = File::create(out_dir.join("wast_testsuite_tests.rs"))
.expect("error generating test source file");
test_directory(&mut out, "misc_testsuite").expect("generating tests");
test_directory(&mut out, "spec_testsuite").expect("generating tests");
}
fn test_directory(out: &mut File, testsuite: &str) -> io::Result<()> {
let mut dir_entries: Vec<_> = read_dir(testsuite)
.expect("reading testsuite directory")
.map(|r| r.expect("reading testsuite directory entry"))
.filter(|dir_entry| {
let p = dir_entry.path();
if let Some(ext) = p.extension() {
// Only look at wast files.
if ext == "wast" {
// Ignore files starting with `.`, which could be editor temporary files
if let Some(stem) = p.file_stem() {
if let Some(stemstr) = stem.to_str() {
if !stemstr.starts_with('.') {
return true;
}
}
}
}
}
false
})
.collect();
dir_entries.sort_by_key(|dir| dir.path());
writeln!(
out,
"mod {} {{",
Path::new(testsuite)
.file_stem()
.expect("testsuite filename should have a stem")
.to_str()
.expect("testsuite filename should be representable as a string")
.replace("-", "_")
)?;
writeln!(out, " use super::{{native_isa, Path, WastContext}};")?;
for dir_entry in dir_entries {
write_testsuite_tests(out, dir_entry, testsuite)?;
}
writeln!(out, "}}")?;
Ok(())
}
fn write_testsuite_tests(out: &mut File, dir_entry: DirEntry, testsuite: &str) -> io::Result<()> {
let path = dir_entry.path();
let stemstr = path
.file_stem()
.expect("file_stem")
.to_str()
.expect("to_str");
writeln!(out, " #[test]")?;
if ignore(testsuite, stemstr) {
writeln!(out, " #[ignore]")?;
}
writeln!(
out,
" fn {}() {{",
avoid_keywords(&stemstr.replace("-", "_"))
)?;
writeln!(out, " let mut wast_context = WastContext::new();")?;
writeln!(out, " let isa = native_isa();")?;
writeln!(out, " wast_context")?;
writeln!(out, " .register_spectest()")?;
writeln!(
out,
" .expect(\"instantiating \\\"spectest\\\"\");"
)?;
writeln!(out, " wast_context")?;
writeln!(
out,
" .run_file(&*isa, Path::new(\"{}\"))",
path.display()
)?;
writeln!(out, " .expect(\"error running wast file\");",)?;
writeln!(out, " }}")?;
writeln!(out)?;
Ok(())
}
/// Rename tests which have the same name as Rust keywords.
fn avoid_keywords(name: &str) -> &str {
match name {
"if" => "if_",
"loop" => "loop_",
"type" => "type_",
"const" => "const_",
"return" => "return_",
other => other,
}
}
/// Ignore tests that aren't supported yet.
fn ignore(_testsuite: &str, _name: &str) -> bool {
false
}

View File

@@ -132,14 +132,6 @@ impl<'module_environment> FuncEnvironment<'module_environment> {
self.isa.frontend_config().pointer_type() self.isa.frontend_config().pointer_type()
} }
/// Transform the call argument list in preparation for making a call.
fn get_real_call_args(func: &Function, call_args: &[ir::Value]) -> Vec<ir::Value> {
let mut real_call_args = Vec::with_capacity(call_args.len() + 1);
real_call_args.extend_from_slice(call_args);
real_call_args.push(func.special_param(ArgumentPurpose::VMContext).unwrap());
real_call_args
}
fn vmctx(&mut self, func: &mut Function) -> ir::GlobalValue { fn vmctx(&mut self, func: &mut Function) -> ir::GlobalValue {
self.vmctx.unwrap_or_else(|| { self.vmctx.unwrap_or_else(|| {
let vmctx = func.create_global_value(ir::GlobalValueData::VMContext); let vmctx = func.create_global_value(ir::GlobalValueData::VMContext);
@@ -539,15 +531,6 @@ impl<'module_environment> cranelift_wasm::FuncEnvironment for FuncEnvironment<'m
let table_entry_addr = pos.ins().table_addr(pointer_type, table, callee, 0); let table_entry_addr = pos.ins().table_addr(pointer_type, table, callee, 0);
// Dereference table_entry_addr to get the function address.
let mem_flags = ir::MemFlags::trusted();
let func_addr = pos.ins().load(
pointer_type,
mem_flags,
table_entry_addr,
i32::from(self.offsets.vmcaller_checked_anyfunc_func_ptr()),
);
// If necessary, check the signature. // If necessary, check the signature.
match self.module.table_plans[table_index].style { match self.module.table_plans[table_index].style {
TableStyle::CallerChecksSignature => { TableStyle::CallerChecksSignature => {
@@ -597,7 +580,27 @@ impl<'module_environment> cranelift_wasm::FuncEnvironment for FuncEnvironment<'m
} }
} }
let real_call_args = FuncEnvironment::get_real_call_args(pos.func, call_args); // Dereference table_entry_addr to get the function address.
let mem_flags = ir::MemFlags::trusted();
let func_addr = pos.ins().load(
pointer_type,
mem_flags,
table_entry_addr,
i32::from(self.offsets.vmcaller_checked_anyfunc_func_ptr()),
);
let mut real_call_args = Vec::with_capacity(call_args.len() + 1);
real_call_args.extend_from_slice(call_args);
// Append the callee vmctx address.
let vmctx = pos.ins().load(
pointer_type,
mem_flags,
table_entry_addr,
i32::from(self.offsets.vmcaller_checked_anyfunc_vmctx()),
);
real_call_args.push(vmctx);
Ok(pos.ins().call_indirect(sig_ref, func_addr, &real_call_args)) Ok(pos.ins().call_indirect(sig_ref, func_addr, &real_call_args))
} }
@@ -608,10 +611,12 @@ impl<'module_environment> cranelift_wasm::FuncEnvironment for FuncEnvironment<'m
callee: ir::FuncRef, callee: ir::FuncRef,
call_args: &[ir::Value], call_args: &[ir::Value],
) -> WasmResult<ir::Inst> { ) -> WasmResult<ir::Inst> {
let real_call_args = FuncEnvironment::get_real_call_args(pos.func, call_args); let mut real_call_args = Vec::with_capacity(call_args.len() + 1);
real_call_args.extend_from_slice(call_args);
// Handle direct calls to locally-defined functions. // Handle direct calls to locally-defined functions.
if !self.module.is_imported_function(callee_index) { if !self.module.is_imported_function(callee_index) {
real_call_args.push(pos.func.special_param(ArgumentPurpose::VMContext).unwrap());
return Ok(pos.ins().call(callee, &real_call_args)); return Ok(pos.ins().call(callee, &real_call_args));
} }
@@ -623,9 +628,18 @@ impl<'module_environment> cranelift_wasm::FuncEnvironment for FuncEnvironment<'m
let base = pos let base = pos
.ins() .ins()
.global_value(pointer_type, imported_functions_base); .global_value(pointer_type, imported_functions_base);
let offset = self.offsets.index_vmfunction_body_import(callee_index);
let mem_flags = ir::MemFlags::trusted(); let mem_flags = ir::MemFlags::trusted();
let func_addr = pos.ins().load(pointer_type, mem_flags, base, offset);
// Load the callee address.
let body_offset = self.offsets.index_vmfunction_import_body(callee_index);
let func_addr = pos.ins().load(pointer_type, mem_flags, base, body_offset);
// Append the callee vmctx address.
let vmctx_offset = self.offsets.index_vmfunction_import_vmctx(callee_index);
let vmctx = pos.ins().load(pointer_type, mem_flags, base, vmctx_offset);
real_call_args.push(vmctx);
Ok(pos.ins().call_indirect(sig_ref, func_addr, &real_call_args)) Ok(pos.ins().call_indirect(sig_ref, func_addr, &real_call_args))
} }

View File

@@ -48,10 +48,10 @@ mod vmoffsets;
pub use compilation::{ pub use compilation::{
compile_module, Compilation, CompileError, RelocSink, Relocation, RelocationTarget, Relocations, compile_module, Compilation, CompileError, RelocSink, Relocation, RelocationTarget, Relocations,
}; };
pub use module::{ pub use module::{Export, MemoryPlan, MemoryStyle, Module, TableElements, TablePlan, TableStyle};
DataInitializer, Export, MemoryPlan, MemoryStyle, Module, TableElements, TablePlan, TableStyle, pub use module_environ::{
translate_signature, DataInitializer, ModuleEnvironment, ModuleTranslation,
}; };
pub use module_environ::{translate_signature, ModuleEnvironment, ModuleTranslation};
pub use tunables::Tunables; pub use tunables::Tunables;
pub use vmoffsets::VMOffsets; pub use vmoffsets::VMOffsets;

View File

@@ -75,7 +75,7 @@ impl MemoryStyle {
/// A WebAssembly linear memory description along with our chosen style for /// A WebAssembly linear memory description along with our chosen style for
/// implementing it. /// implementing it.
#[derive(Debug)] #[derive(Debug, Clone)]
pub struct MemoryPlan { pub struct MemoryPlan {
/// The WebAssembly linear memory description. /// The WebAssembly linear memory description.
pub memory: Memory, pub memory: Memory,
@@ -113,7 +113,7 @@ impl TableStyle {
/// A WebAssembly table description along with our chosen style for /// A WebAssembly table description along with our chosen style for
/// implementing it. /// implementing it.
#[derive(Debug)] #[derive(Debug, Clone)]
pub struct TablePlan { pub struct TablePlan {
/// The WebAssembly table description. /// The WebAssembly table description.
pub table: cranelift_wasm::Table, pub table: cranelift_wasm::Table,
@@ -277,34 +277,3 @@ impl Module {
index.index() < self.imported_globals.len() index.index() < self.imported_globals.len()
} }
} }
/// A data initializer for linear memory.
pub struct DataInitializer<'data> {
/// The index of the memory to initialize.
pub memory_index: MemoryIndex,
/// Optionally a globalvar base to initialize at.
pub base: Option<GlobalIndex>,
/// A constant offset to initialize at.
pub offset: usize,
/// The initialization data.
pub data: &'data [u8],
}
/// References to the input wasm data buffer to be decoded and processed later,
/// separately from the main module translation.
pub struct LazyContents<'data> {
/// References to the function bodies.
pub function_body_inputs: PrimaryMap<DefinedFuncIndex, &'data [u8]>,
/// References to the data initializers.
pub data_initializers: Vec<DataInitializer<'data>>,
}
impl<'data> LazyContents<'data> {
pub fn new() -> Self {
Self {
function_body_inputs: PrimaryMap::new(),
data_initializers: Vec::new(),
}
}
}

View File

@@ -1,12 +1,13 @@
use cranelift_codegen::ir; use cranelift_codegen::ir;
use cranelift_codegen::ir::{AbiParam, ArgumentPurpose}; use cranelift_codegen::ir::{AbiParam, ArgumentPurpose};
use cranelift_codegen::isa; use cranelift_codegen::isa;
use cranelift_entity::PrimaryMap;
use cranelift_wasm::{ use cranelift_wasm::{
self, translate_module, FuncIndex, Global, GlobalIndex, Memory, MemoryIndex, SignatureIndex, self, translate_module, DefinedFuncIndex, FuncIndex, Global, GlobalIndex, Memory, MemoryIndex,
Table, TableIndex, WasmResult, SignatureIndex, Table, TableIndex, WasmResult,
}; };
use func_environ::FuncEnvironment; use func_environ::FuncEnvironment;
use module::{DataInitializer, Export, LazyContents, MemoryPlan, Module, TableElements, TablePlan}; use module::{Export, MemoryPlan, Module, TableElements, TablePlan};
use std::clone::Clone; use std::clone::Clone;
use std::string::String; use std::string::String;
use std::vec::Vec; use std::vec::Vec;
@@ -259,3 +260,34 @@ pub fn translate_signature(mut sig: ir::Signature, pointer_type: ir::Type) -> ir
.push(AbiParam::special(pointer_type, ArgumentPurpose::VMContext)); .push(AbiParam::special(pointer_type, ArgumentPurpose::VMContext));
sig sig
} }
/// A data initializer for linear memory.
pub struct DataInitializer<'data> {
/// The index of the memory to initialize.
pub memory_index: MemoryIndex,
/// Optionally a globalvar base to initialize at.
pub base: Option<GlobalIndex>,
/// A constant offset to initialize at.
pub offset: usize,
/// The initialization data.
pub data: &'data [u8],
}
/// References to the input wasm data buffer to be decoded and processed later,
/// separately from the main module translation.
pub struct LazyContents<'data> {
/// References to the function bodies.
pub function_body_inputs: PrimaryMap<DefinedFuncIndex, &'data [u8]>,
/// References to the data initializers.
pub data_initializers: Vec<DataInitializer<'data>>,
}
impl<'data> LazyContents<'data> {
pub fn new() -> Self {
Self {
function_body_inputs: PrimaryMap::new(),
data_initializers: Vec::new(),
}
}
}

View File

@@ -20,6 +20,26 @@ impl VMOffsets {
} }
} }
/// Offsets for `VMFunctionImport`.
impl VMOffsets {
/// The offset of the `body` field.
#[allow(clippy::erasing_op)]
pub fn vmfunction_import_body(&self) -> u8 {
0 * self.pointer_size
}
/// The offset of the `vmctx` field.
#[allow(clippy::identity_op)]
pub fn vmfunction_import_vmctx(&self) -> u8 {
1 * self.pointer_size
}
/// Return the size of `VMFunctionImport`.
pub fn size_of_vmfunction_import(&self) -> u8 {
2 * self.pointer_size
}
}
/// Offsets for `*const VMFunctionBody`. /// Offsets for `*const VMFunctionBody`.
impl VMOffsets { impl VMOffsets {
/// The size of the `current_elements` field. /// The size of the `current_elements` field.
@@ -174,9 +194,14 @@ impl VMOffsets {
1 * self.pointer_size 1 * self.pointer_size
} }
/// The offset of the `vmctx` field.
pub fn vmcaller_checked_anyfunc_vmctx(&self) -> u8 {
2 * self.pointer_size
}
/// Return the size of `VMCallerCheckedAnyfunc`. /// Return the size of `VMCallerCheckedAnyfunc`.
pub fn size_of_vmcaller_checked_anyfunc(&self) -> u8 { pub fn size_of_vmcaller_checked_anyfunc(&self) -> u8 {
2 * self.pointer_size 3 * self.pointer_size
} }
} }
@@ -230,6 +255,17 @@ impl VMOffsets {
8 * self.pointer_size 8 * self.pointer_size
} }
/// Return the offset from the `imported_functions` pointer to `VMFunctionImport` index `index`.
fn index_vmfunction_import(&self, index: FuncIndex) -> i32 {
cast::i32(
index
.as_u32()
.checked_mul(u32::from(self.size_of_vmfunction_import()))
.unwrap(),
)
.unwrap()
}
/// Return the offset from the `imported_tables` pointer to `VMTableImport` index `index`. /// Return the offset from the `imported_tables` pointer to `VMTableImport` index `index`.
fn index_vmtable_import(&self, index: TableIndex) -> i32 { fn index_vmtable_import(&self, index: TableIndex) -> i32 {
cast::i32( cast::i32(
@@ -286,14 +322,18 @@ impl VMOffsets {
} }
/// Return the offset from the `imported_functions` pointer to the /// Return the offset from the `imported_functions` pointer to the
/// `*const VMFunctionBody` index `index`. /// `body` field in `*const VMFunctionBody` index `index`.
pub fn index_vmfunction_body_import(&self, index: FuncIndex) -> i32 { pub fn index_vmfunction_import_body(&self, index: FuncIndex) -> i32 {
cast::i32( self.index_vmfunction_import(index)
index .checked_add(i32::from(self.vmfunction_import_body()))
.as_u32() .unwrap()
.checked_mul(u32::from(self.size_of_vmfunction_body_ptr())) }
.unwrap(),
) /// Return the offset from the `imported_functions` pointer to the
/// `vmctx` field in `*const VMFunctionBody` index `index`.
pub fn index_vmfunction_import_vmctx(&self, index: FuncIndex) -> i32 {
self.index_vmfunction_import(index)
.checked_add(i32::from(self.vmfunction_import_vmctx()))
.unwrap() .unwrap()
} }

View File

@@ -6,6 +6,7 @@ use std::fmt;
use std::string::String; use std::string::String;
use std::vec::Vec; use std::vec::Vec;
use wasmtime_environ::CompileError; use wasmtime_environ::CompileError;
use wasmtime_runtime::InstantiationError;
/// A runtime value. /// A runtime value.
#[derive(Copy, Clone, Debug, Eq, PartialEq)] #[derive(Copy, Clone, Debug, Eq, PartialEq)]
@@ -110,10 +111,6 @@ pub enum ActionError {
#[fail(display = "Unknown field: {}", _0)] #[fail(display = "Unknown field: {}", _0)]
Field(String), 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). /// The field was present but was the wrong kind (eg. function, table, global, or memory).
#[fail(display = "Kind error: {}", _0)] #[fail(display = "Kind error: {}", _0)]
Kind(String), Kind(String),
@@ -126,9 +123,10 @@ pub enum ActionError {
#[fail(display = "WebAssembly compilation error: {}", _0)] #[fail(display = "WebAssembly compilation error: {}", _0)]
Compile(CompileError), Compile(CompileError),
/// Some runtime resource was unavailable or insufficient. /// Some runtime resource was unavailable or insufficient, or the start function
#[fail(display = "Runtime resource error: {}", _0)] /// trapped.
Resource(String), #[fail(display = "Instantiation error: {}", _0)]
Instantiate(InstantiationError),
/// Link error. /// Link error.
#[fail(display = "Link error: {}", _0)] #[fail(display = "Link error: {}", _0)]

View 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)
}

View File

@@ -7,15 +7,15 @@ use std::{cmp, mem};
use wasmtime_runtime::{Mmap, VMFunctionBody}; use wasmtime_runtime::{Mmap, VMFunctionBody};
/// Memory manager for executable code. /// Memory manager for executable code.
pub struct Code { pub struct JITCode {
current: Mmap, current: Mmap,
mmaps: Vec<Mmap>, mmaps: Vec<Mmap>,
position: usize, position: usize,
published: usize, published: usize,
} }
impl Code { impl JITCode {
/// Create a new `Code` instance. /// Create a new `JITCode` instance.
pub fn new() -> Self { pub fn new() -> Self {
Self { Self {
current: Mmap::new(), current: Mmap::new(),

View File

@@ -39,16 +39,17 @@ extern crate failure;
extern crate failure_derive; extern crate failure_derive;
mod action; mod action;
mod code; mod instance_plus;
mod export; mod jit_code;
mod link; mod link;
mod world; mod resolver;
mod trampoline_park;
pub use action::{ActionError, ActionOutcome, RuntimeValue}; pub use action::{ActionError, ActionOutcome, RuntimeValue};
pub use code::Code; pub use instance_plus::InstancePlus;
pub use export::{Export, NullResolver, Resolver}; pub use jit_code::JITCode;
pub use link::link_module; pub use link::link_module;
pub use world::InstanceWorld; pub use resolver::{NullResolver, Resolver};
#[cfg(not(feature = "std"))] #[cfg(not(feature = "std"))]
mod std { mod std {

View File

@@ -1,16 +1,18 @@
use cranelift_codegen::binemit::Reloc; use cranelift_codegen::binemit::Reloc;
use cranelift_entity::PrimaryMap; use cranelift_entity::PrimaryMap;
use cranelift_wasm::{DefinedFuncIndex, Global, GlobalInit, Memory, Table, TableElementType}; use cranelift_wasm::{DefinedFuncIndex, Global, GlobalInit, Memory, Table, TableElementType};
use export::{Export, FunctionExport, Resolver}; use resolver::Resolver;
use std::ptr::write_unaligned; use std::ptr::write_unaligned;
use std::string::String; use std::string::String;
use std::vec::Vec; use std::vec::Vec;
use wasmtime_environ::{ use wasmtime_environ::{
MemoryPlan, MemoryStyle, Module, Relocation, RelocationTarget, Relocations, TablePlan, MemoryPlan, MemoryStyle, Module, Relocation, RelocationTarget, Relocations, TablePlan,
TableStyle,
}; };
use wasmtime_runtime::libcalls; 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. /// A link error, such as incompatible or unmatched imports/exports.
#[derive(Fail, Debug)] #[derive(Fail, Debug)]
@@ -28,7 +30,11 @@ pub fn link_module(
for (index, (ref module_name, ref field)) in module.imported_funcs.iter() { for (index, (ref module_name, ref field)) in module.imported_funcs.iter() {
match resolver.resolve(module_name, field) { match resolver.resolve(module_name, field) {
Some(export_value) => match export_value { Some(export_value) => match export_value {
Export::Function(FunctionExport { address, signature }) => { Export::Function {
address,
signature,
vmctx,
} => {
let import_signature = &module.signatures[module.functions[index]]; let import_signature = &module.signatures[module.functions[index]];
if signature != *import_signature { if signature != *import_signature {
// TODO: If the difference is in the calling convention, // TODO: If the difference is in the calling convention,
@@ -39,7 +45,10 @@ pub fn link_module(
signature, import_signature) signature, import_signature)
)); ));
} }
function_imports.push(address); function_imports.push(VMFunctionImport {
body: address,
vmctx,
});
} }
Export::Table { .. } | Export::Memory { .. } | Export::Global { .. } => { Export::Table { .. } | Export::Memory { .. } | Export::Global { .. } => {
return Err(LinkError(format!( return Err(LinkError(format!(
@@ -104,12 +113,28 @@ pub fn link_module(
memory, memory,
} => { } => {
let import_memory = &module.memory_plans[index]; 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!( return Err(LinkError(format!(
"{}/{}: exported memory incompatible with memory import", "{}/{}: exported memory incompatible with memory import",
module_name, field 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 { memory_imports.push(VMMemoryImport {
from: address, from: address,
vmctx, 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, function_imports,
table_imports, table_imports,
memory_imports, memory_imports,
global_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 { 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 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( fn is_table_element_type_compatible(
exported_type: TableElementType, exported_type: TableElementType,
imported_type: TableElementType, imported_type: TableElementType,
@@ -225,7 +240,7 @@ fn is_table_compatible(exported: &TablePlan, imported: &TablePlan) -> bool {
minimum: exported_minimum, minimum: exported_minimum,
maximum: exported_maximum, maximum: exported_maximum,
}, },
style: exported_style, style: _exported_style,
} = exported; } = exported;
let TablePlan { let TablePlan {
table: table:
@@ -234,30 +249,14 @@ fn is_table_compatible(exported: &TablePlan, imported: &TablePlan) -> bool {
minimum: imported_minimum, minimum: imported_minimum,
maximum: imported_maximum, maximum: imported_maximum,
}, },
style: imported_style, style: _imported_style,
} = imported; } = imported;
is_table_element_type_compatible(*exported_ty, *imported_ty) is_table_element_type_compatible(*exported_ty, *imported_ty)
&& imported_minimum >= exported_minimum && imported_minimum <= exported_minimum
&& imported_maximum <= exported_maximum && (imported_maximum.is_none()
&& is_table_style_compatible(imported_style, exported_style) || (!exported_maximum.is_none()
} && imported_maximum.unwrap() >= exported_maximum.unwrap()))
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,
},
}
} }
fn is_memory_compatible(exported: &MemoryPlan, imported: &MemoryPlan) -> bool { 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, maximum: exported_maximum,
shared: exported_shared, shared: exported_shared,
}, },
style: exported_style, style: _exported_style,
offset_guard_size: exported_offset_guard_size, offset_guard_size: _exported_offset_guard_size,
} = exported; } = exported;
let MemoryPlan { let MemoryPlan {
memory: memory:
@@ -278,20 +277,19 @@ fn is_memory_compatible(exported: &MemoryPlan, imported: &MemoryPlan) -> bool {
maximum: imported_maximum, maximum: imported_maximum,
shared: imported_shared, shared: imported_shared,
}, },
style: imported_style, style: _imported_style,
offset_guard_size: imported_offset_guard_size, offset_guard_size: _imported_offset_guard_size,
} = imported; } = imported;
imported_minimum >= exported_minimum imported_minimum <= exported_minimum
&& imported_maximum <= exported_maximum && (imported_maximum.is_none()
|| (!exported_maximum.is_none()
&& imported_maximum.unwrap() >= exported_maximum.unwrap()))
&& exported_shared == imported_shared && 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. /// Performs the relocations inside the function bytecode, provided the necessary metadata.
fn relocate( fn relocate(
imports: &Imports,
allocated_functions: &PrimaryMap<DefinedFuncIndex, *mut [VMFunctionBody]>, allocated_functions: &PrimaryMap<DefinedFuncIndex, *mut [VMFunctionBody]>,
relocations: PrimaryMap<DefinedFuncIndex, Vec<Relocation>>, relocations: PrimaryMap<DefinedFuncIndex, Vec<Relocation>>,
module: &Module, module: &Module,
@@ -305,7 +303,7 @@ fn relocate(
let fatptr: *const [VMFunctionBody] = allocated_functions[f]; let fatptr: *const [VMFunctionBody] = allocated_functions[f];
fatptr as *const VMFunctionBody as usize 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::Memory32Grow => wasmtime_memory32_grow as usize,
RelocationTarget::Memory32Size => wasmtime_memory32_size as usize, RelocationTarget::Memory32Size => wasmtime_memory32_size as usize,

View 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
}
}

View 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())
}

View File

@@ -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())
}

View File

@@ -1,22 +1,21 @@
use cranelift_codegen::ir; use cranelift_codegen::ir;
use cranelift_wasm::Global; use cranelift_wasm::Global;
use wasmtime_environ::{MemoryPlan, TablePlan}; use vmcontext::{
use wasmtime_runtime::{
VMContext, VMFunctionBody, VMGlobalDefinition, VMMemoryDefinition, VMTableDefinition, VMContext, VMFunctionBody, VMGlobalDefinition, VMMemoryDefinition, VMTableDefinition,
}; };
use wasmtime_environ::{MemoryPlan, TablePlan};
/// 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. /// The value of an export passed from one instance to another.
pub enum Export { pub enum Export {
/// A function export value. /// A function export value.
Function(FunctionExport), Function {
/// The address of the native-code function.
address: *const VMFunctionBody,
/// The function signature declaration, used for compatibilty checking.
signature: ir::Signature,
/// Pointer to the containing VMContext.
vmctx: *mut VMContext,
},
/// A table export value. /// A table export value.
Table { Table {
@@ -49,8 +48,16 @@ pub enum Export {
impl Export { impl Export {
/// Construct a function export value. /// Construct a function export value.
pub fn function(address: *const VMFunctionBody, signature: ir::Signature) -> Self { pub fn function(
Export::Function(FunctionExport { address, signature }) address: *const VMFunctionBody,
signature: ir::Signature,
vmctx: *mut VMContext,
) -> Self {
Export::Function {
address,
signature,
vmctx,
}
} }
/// Construct a table export value. /// Construct a table export value.
@@ -80,18 +87,3 @@ impl Export {
Export::Global { address, global } 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
}
}

View File

@@ -1,12 +1,12 @@
use cranelift_entity::{BoxedSlice, PrimaryMap}; use cranelift_entity::{BoxedSlice, PrimaryMap};
use cranelift_wasm::{FuncIndex, GlobalIndex, MemoryIndex, TableIndex}; use cranelift_wasm::{FuncIndex, GlobalIndex, MemoryIndex, TableIndex};
use vmcontext::{VMFunctionBody, VMGlobalImport, VMMemoryImport, VMTableImport}; use vmcontext::{VMFunctionImport, VMGlobalImport, VMMemoryImport, VMTableImport};
/// Resolved import pointers. /// Resolved import pointers.
#[derive(Debug)] #[derive(Debug)]
pub struct Imports { pub struct Imports {
/// Resolved addresses for imported functions. /// Resolved addresses for imported functions.
pub functions: BoxedSlice<FuncIndex, *const VMFunctionBody>, pub functions: BoxedSlice<FuncIndex, VMFunctionImport>,
/// Resolved addresses for imported tables. /// Resolved addresses for imported tables.
pub tables: BoxedSlice<TableIndex, VMTableImport>, pub tables: BoxedSlice<TableIndex, VMTableImport>,
@@ -21,7 +21,7 @@ pub struct Imports {
impl Imports { impl Imports {
/// Construct a new `Imports` instance. /// Construct a new `Imports` instance.
pub fn new( pub fn new(
function_imports: PrimaryMap<FuncIndex, *const VMFunctionBody>, function_imports: PrimaryMap<FuncIndex, VMFunctionImport>,
table_imports: PrimaryMap<TableIndex, VMTableImport>, table_imports: PrimaryMap<TableIndex, VMTableImport>,
memory_imports: PrimaryMap<MemoryIndex, VMMemoryImport>, memory_imports: PrimaryMap<MemoryIndex, VMMemoryImport>,
global_imports: PrimaryMap<GlobalIndex, VMGlobalImport>, global_imports: PrimaryMap<GlobalIndex, VMGlobalImport>,

View File

@@ -4,13 +4,18 @@
use cranelift_entity::EntityRef; use cranelift_entity::EntityRef;
use cranelift_entity::{BoxedSlice, PrimaryMap}; use cranelift_entity::{BoxedSlice, PrimaryMap};
use cranelift_wasm::{ use cranelift_wasm::{
DefinedFuncIndex, DefinedGlobalIndex, DefinedMemoryIndex, DefinedTableIndex, FuncIndex, DefinedFuncIndex, DefinedGlobalIndex, DefinedMemoryIndex, DefinedTableIndex, GlobalInit,
}; };
use export::Export;
use imports::Imports; use imports::Imports;
use memory::LinearMemory; use memory::LinearMemory;
use sig_registry::SignatureRegistry; use sig_registry::SignatureRegistry;
use signalhandlers::{wasmtime_init_eager, wasmtime_init_finish};
use std::rc::Rc;
use std::slice;
use std::string::String; use std::string::String;
use table::Table; use table::Table;
use traphandlers::wasmtime_call;
use vmcontext::{ use vmcontext::{
VMCallerCheckedAnyfunc, VMContext, VMFunctionBody, VMGlobalDefinition, VMMemoryDefinition, VMCallerCheckedAnyfunc, VMContext, VMFunctionBody, VMGlobalDefinition, VMMemoryDefinition,
VMTableDefinition, VMTableDefinition,
@@ -20,6 +25,9 @@ use wasmtime_environ::{DataInitializer, Module};
/// An Instance of a WebAssemby module. /// An Instance of a WebAssemby module.
#[derive(Debug)] #[derive(Debug)]
pub struct Instance { pub struct Instance {
/// The `Module` this `Instance` was instantiated from.
module: Rc<Module>,
/// WebAssembly linear memory data. /// WebAssembly linear memory data.
memories: BoxedSlice<DefinedMemoryIndex, LinearMemory>, memories: BoxedSlice<DefinedMemoryIndex, LinearMemory>,
@@ -33,6 +41,9 @@ pub struct Instance {
/// Resolved imports. /// Resolved imports.
vmctx_imports: Imports, vmctx_imports: Imports,
/// Pointers to functions in executable memory.
finished_functions: BoxedSlice<DefinedFuncIndex, *const VMFunctionBody>,
/// Table storage base address vector pointed to by vmctx. /// Table storage base address vector pointed to by vmctx.
vmctx_tables: BoxedSlice<DefinedTableIndex, VMTableDefinition>, vmctx_tables: BoxedSlice<DefinedTableIndex, VMTableDefinition>,
@@ -49,19 +60,20 @@ pub struct Instance {
impl Instance { impl Instance {
/// Create a new `Instance`. /// Create a new `Instance`.
pub fn new( pub fn new(
module: &Module, module: Rc<Module>,
finished_functions: &BoxedSlice<DefinedFuncIndex, *const VMFunctionBody>, finished_functions: BoxedSlice<DefinedFuncIndex, *const VMFunctionBody>,
mut vmctx_imports: Imports, mut vmctx_imports: Imports,
data_initializers: &[DataInitializer], data_initializers: Vec<DataInitializer>,
) -> Result<Self, String> { ) -> Result<Box<Self>, InstantiationError> {
let mut sig_registry = instantiate_signatures(module); let mut sig_registry = create_and_initialize_signatures(&module);
let mut memories = instantiate_memories(module, data_initializers)?; let mut tables = create_tables(&module);
let mut tables = instantiate_tables( let mut memories = create_memories(&module)?;
module,
finished_functions, let mut vmctx_tables = tables
&vmctx_imports.functions, .values_mut()
&mut sig_registry, .map(Table::vmtable)
); .collect::<PrimaryMap<DefinedTableIndex, _>>()
.into_boxed_slice();
let mut vmctx_memories = memories let mut vmctx_memories = memories
.values_mut() .values_mut()
@@ -69,13 +81,7 @@ impl Instance {
.collect::<PrimaryMap<DefinedMemoryIndex, _>>() .collect::<PrimaryMap<DefinedMemoryIndex, _>>()
.into_boxed_slice(); .into_boxed_slice();
let mut vmctx_globals = instantiate_globals(module); let mut vmctx_globals = create_globals(&module);
let mut vmctx_tables = tables
.values_mut()
.map(Table::vmtable)
.collect::<PrimaryMap<DefinedTableIndex, _>>()
.into_boxed_slice();
let vmctx_imported_functions_ptr = vmctx_imports let vmctx_imported_functions_ptr = vmctx_imports
.functions .functions
@@ -90,19 +96,21 @@ impl Instance {
.as_mut_ptr(); .as_mut_ptr();
let vmctx_imported_globals_ptr = let vmctx_imported_globals_ptr =
vmctx_imports.globals.values_mut().into_slice().as_mut_ptr(); vmctx_imports.globals.values_mut().into_slice().as_mut_ptr();
let vmctx_tables_ptr = vmctx_tables.values_mut().into_slice().as_mut_ptr();
let vmctx_memories_ptr = vmctx_memories.values_mut().into_slice().as_mut_ptr(); let vmctx_memories_ptr = vmctx_memories.values_mut().into_slice().as_mut_ptr();
let vmctx_globals_ptr = vmctx_globals.values_mut().into_slice().as_mut_ptr(); let vmctx_globals_ptr = vmctx_globals.values_mut().into_slice().as_mut_ptr();
let vmctx_tables_ptr = vmctx_tables.values_mut().into_slice().as_mut_ptr();
let vmctx_shared_signatures_ptr = sig_registry.vmshared_signatures(); let vmctx_shared_signatures_ptr = sig_registry.vmshared_signatures();
Ok(Self { let mut result = Box::new(Self {
module,
memories, memories,
tables, tables,
sig_registry, sig_registry,
vmctx_imports, vmctx_imports,
finished_functions,
vmctx_tables,
vmctx_memories, vmctx_memories,
vmctx_globals, vmctx_globals,
vmctx_tables,
vmctx: VMContext::new( vmctx: VMContext::new(
vmctx_imported_functions_ptr, vmctx_imported_functions_ptr,
vmctx_imported_tables_ptr, vmctx_imported_tables_ptr,
@@ -113,7 +121,31 @@ impl Instance {
vmctx_globals_ptr, vmctx_globals_ptr,
vmctx_shared_signatures_ptr, vmctx_shared_signatures_ptr,
), ),
}) });
// Check initializer bounds before initializing anything.
check_table_init_bounds(&mut *result)?;
check_memory_init_bounds(&mut *result, &data_initializers)?;
// Apply the initializers.
initialize_tables(&mut *result)?;
initialize_memories(&mut *result, data_initializers)?;
initialize_globals(&mut *result);
// 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.
// TODO: Move these calls out of `Instance`.
wasmtime_init_eager();
wasmtime_init_finish(result.vmctx_mut());
// The WebAssembly spec specifies that the start function is
// invoked automatically at instantiation time.
result.invoke_start_function()?;
Ok(result)
} }
/// Return a reference to the vmctx used by JIT code. /// Return a reference to the vmctx used by JIT code.
@@ -121,11 +153,21 @@ impl Instance {
&self.vmctx &self.vmctx
} }
/// Return a raw pointer to the vmctx used by JIT code.
pub fn vmctx_ptr(&self) -> *const VMContext {
self.vmctx()
}
/// Return a mutable reference to the vmctx used by JIT code. /// Return a mutable reference to the vmctx used by JIT code.
pub fn vmctx_mut(&mut self) -> &mut VMContext { pub fn vmctx_mut(&mut self) -> &mut VMContext {
&mut self.vmctx &mut self.vmctx
} }
/// Return a mutable raw pointer to the vmctx used by JIT code.
pub fn vmctx_mut_ptr(&mut self) -> *mut VMContext {
self.vmctx_mut()
}
/// Return the offset from the vmctx pointer to its containing Instance. /// Return the offset from the vmctx pointer to its containing Instance.
pub(crate) fn vmctx_offset() -> isize { pub(crate) fn vmctx_offset() -> isize {
offset_of!(Self, vmctx) as isize offset_of!(Self, vmctx) as isize
@@ -166,11 +208,195 @@ impl Instance {
/// Return the number of imported memories. /// Return the number of imported memories.
pub(crate) fn num_imported_memories(&self) -> usize { pub(crate) fn num_imported_memories(&self) -> usize {
self.vmctx_imports.functions.len() self.vmctx_imports.memories.len()
}
/// Invoke the WebAssembly start function of the instance, if one is present.
fn invoke_start_function(&mut self) -> Result<(), InstantiationError> {
if let Some(start_index) = self.module.start_func {
let (callee_address, callee_vmctx) = match self.module.defined_func_index(start_index) {
Some(defined_start_index) => {
let body = self
.finished_functions
.get(defined_start_index)
.expect("start function index is out of bounds")
.clone();
(body, self.vmctx_mut() as *mut VMContext)
}
None => {
assert!(start_index.index() < self.module.imported_funcs.len());
let import = unsafe { self.vmctx.imported_function(start_index) };
(import.body, import.vmctx)
}
};
// Make the call.
unsafe { wasmtime_call(callee_address, callee_vmctx) }
.map_err(InstantiationError::StartTrap)?;
}
Ok(())
}
/// Lookup an export with the given name.
pub fn lookup(&mut self, field: &str) -> Option<Export> {
if let Some(export) = self.module.exports.get(field) {
Some(match export {
wasmtime_environ::Export::Function(index) => {
let signature = self.module.signatures[self.module.functions[*index]].clone();
let (address, vmctx) =
if let Some(def_index) = self.module.defined_func_index(*index) {
(
self.finished_functions[def_index],
&mut self.vmctx as *mut VMContext,
)
} else {
let import = unsafe { self.vmctx.imported_function(*index) };
(import.body, import.vmctx)
};
Export::Function {
address,
signature,
vmctx,
}
}
wasmtime_environ::Export::Table(index) => {
let (address, vmctx) = if let Some(def_index) =
self.module.defined_table_index(*index)
{
(
unsafe { self.vmctx.table_mut(def_index) } as *mut VMTableDefinition,
&mut self.vmctx as *mut VMContext,
)
} else {
let import = unsafe { self.vmctx.imported_table(*index) };
(import.from, import.vmctx)
};
Export::Table {
address,
vmctx,
table: self.module.table_plans[*index].clone(),
}
}
wasmtime_environ::Export::Memory(index) => {
let (address, vmctx) = if let Some(def_index) =
self.module.defined_memory_index(*index)
{
(
unsafe { self.vmctx.memory_mut(def_index) } as *mut VMMemoryDefinition,
&mut self.vmctx as *mut VMContext,
)
} else {
let import = unsafe { self.vmctx.imported_memory(*index) };
(import.from, import.vmctx)
};
Export::Memory {
address,
vmctx,
memory: self.module.memory_plans[*index].clone(),
}
}
wasmtime_environ::Export::Global(index) => Export::Global {
address: if let Some(def_index) = self.module.defined_global_index(*index) {
unsafe { self.vmctx.global_mut(def_index) }
} else {
unsafe { self.vmctx.imported_global(*index).from }
},
global: self.module.globals[*index].clone(),
},
})
} else {
None
}
}
/// Lookup an export with the given name. This takes an immutable reference,
/// and the result is an `Export` that can only be used to read, not write.
/// This requirement is not enforced in the type system, so this function is
/// unsafe.
pub unsafe fn lookup_immutable(&self, field: &str) -> Option<Export> {
let temporary_mut = &mut *(self as *const Instance as *mut Instance);
temporary_mut.lookup(field)
} }
} }
fn instantiate_signatures(module: &Module) -> SignatureRegistry { fn check_table_init_bounds(instance: &mut Instance) -> Result<(), InstantiationError> {
for init in &instance.module.table_elements {
// TODO: Refactor this.
let mut start = init.offset;
if let Some(base) = init.base {
let global = if let Some(def_index) = instance.module.defined_global_index(base) {
unsafe { instance.vmctx.global_mut(def_index) }
} else {
unsafe { instance.vmctx.imported_global(base).from }
};
start += unsafe { *(&*global).as_i32() } as u32 as usize;
}
// TODO: Refactor this.
let slice = if let Some(defined_table_index) =
instance.module.defined_table_index(init.table_index)
{
instance.tables[defined_table_index].as_mut()
} else {
let import = &instance.vmctx_imports.tables[init.table_index];
let foreign_instance = unsafe { (&mut *(import).vmctx).instance() };
let foreign_table = unsafe { &mut *(import).from };
let foreign_index = foreign_instance.vmctx().table_index(foreign_table);
foreign_instance.tables[foreign_index].as_mut()
};
if slice.get_mut(start..start + init.elements.len()).is_none() {
return Err(InstantiationError::Link(
"elements segment does not fit".to_owned(),
));
}
}
Ok(())
}
fn check_memory_init_bounds(
instance: &mut Instance,
data_initializers: &[DataInitializer],
) -> Result<(), InstantiationError> {
for init in data_initializers {
// TODO: Refactor this.
let mut start = init.offset;
if let Some(base) = init.base {
let global = if let Some(def_index) = instance.module.defined_global_index(base) {
unsafe { instance.vmctx.global_mut(def_index) }
} else {
unsafe { instance.vmctx.imported_global(base).from }
};
start += unsafe { *(&*global).as_i32() } as u32 as usize;
}
// TODO: Refactor this.
let memory = if let Some(defined_memory_index) =
instance.module.defined_memory_index(init.memory_index)
{
unsafe { instance.vmctx.memory(defined_memory_index) }
} else {
let import = &instance.vmctx_imports.memories[init.memory_index];
let foreign_instance = unsafe { (&mut *(import).vmctx).instance() };
let foreign_memory = unsafe { &mut *(import).from };
let foreign_index = foreign_instance.vmctx().memory_index(foreign_memory);
unsafe { foreign_instance.vmctx.memory(foreign_index) }
};
let mem_slice = unsafe { slice::from_raw_parts_mut(memory.base, memory.current_length) };
if mem_slice.get_mut(start..start + init.data.len()).is_none() {
return Err(InstantiationError::Link(
"data segment does not fit".to_owned(),
));
}
}
Ok(())
}
fn create_and_initialize_signatures(module: &Module) -> SignatureRegistry {
let mut sig_registry = SignatureRegistry::new(); let mut sig_registry = SignatureRegistry::new();
for (sig_index, sig) in module.signatures.iter() { for (sig_index, sig) in module.signatures.iter() {
sig_registry.register(sig_index, sig); sig_registry.register(sig_index, sig);
@@ -179,78 +405,169 @@ fn instantiate_signatures(module: &Module) -> SignatureRegistry {
} }
/// Allocate memory for just the tables of the current module. /// Allocate memory for just the tables of the current module.
fn instantiate_tables( fn create_tables(module: &Module) -> BoxedSlice<DefinedTableIndex, Table> {
module: &Module, let num_imports = module.imported_tables.len();
finished_functions: &BoxedSlice<DefinedFuncIndex, *const VMFunctionBody>,
imported_functions: &BoxedSlice<FuncIndex, *const VMFunctionBody>,
sig_registry: &mut SignatureRegistry,
) -> BoxedSlice<DefinedTableIndex, Table> {
let num_imports = module.imported_memories.len();
let mut tables: PrimaryMap<DefinedTableIndex, _> = let mut tables: PrimaryMap<DefinedTableIndex, _> =
PrimaryMap::with_capacity(module.table_plans.len() - num_imports); PrimaryMap::with_capacity(module.table_plans.len() - num_imports);
for table in &module.table_plans.values().as_slice()[num_imports..] { for table in &module.table_plans.values().as_slice()[num_imports..] {
tables.push(Table::new(table)); tables.push(Table::new(table));
} }
for init in &module.table_elements {
debug_assert!(init.base.is_none(), "globalvar base not supported yet");
let defined_table_index = module
.defined_table_index(init.table_index)
.expect("Initializers for imported tables not supported yet");
let slice = tables[defined_table_index].as_mut();
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 func_ptr = if let Some(index) = module.defined_func_index(*func_idx) {
finished_functions[index]
} else {
imported_functions[*func_idx]
};
let type_index = sig_registry.lookup(callee_sig);
subslice[i] = VMCallerCheckedAnyfunc {
func_ptr,
type_index,
};
}
}
tables.into_boxed_slice() tables.into_boxed_slice()
} }
/// Initialize the table memory from the provided initializers.
fn initialize_tables(instance: &mut Instance) -> Result<(), InstantiationError> {
let vmctx: *mut VMContext = instance.vmctx_mut();
for init in &instance.module.table_elements {
let mut start = init.offset;
if let Some(base) = init.base {
let global = if let Some(def_index) = instance.module.defined_global_index(base) {
unsafe { instance.vmctx.global_mut(def_index) }
} else {
unsafe { instance.vmctx.imported_global(base).from }
};
start += unsafe { *(&*global).as_i32() } as u32 as usize;
}
let slice = if let Some(defined_table_index) =
instance.module.defined_table_index(init.table_index)
{
instance.tables[defined_table_index].as_mut()
} else {
let import = &instance.vmctx_imports.tables[init.table_index];
let foreign_instance = unsafe { (&mut *(import).vmctx).instance() };
let foreign_table = unsafe { &mut *(import).from };
let foreign_index = foreign_instance.vmctx().table_index(foreign_table);
foreign_instance.tables[foreign_index].as_mut()
};
if let Some(subslice) = slice.get_mut(start..start + init.elements.len()) {
for (i, func_idx) in init.elements.iter().enumerate() {
let callee_sig = instance.module.functions[*func_idx];
let (callee_ptr, callee_vmctx) =
if let Some(index) = instance.module.defined_func_index(*func_idx) {
(instance.finished_functions[index], vmctx)
} else {
let imported_func = &instance.vmctx_imports.functions[*func_idx];
(imported_func.body, imported_func.vmctx)
};
let type_index = instance.sig_registry.lookup(callee_sig);
subslice[i] = VMCallerCheckedAnyfunc {
func_ptr: callee_ptr,
type_index,
vmctx: callee_vmctx,
};
}
} else {
return Err(InstantiationError::Link(
"elements segment does not fit".to_owned(),
));
}
}
Ok(())
}
/// Allocate memory for just the memories of the current module. /// Allocate memory for just the memories of the current module.
fn instantiate_memories( fn create_memories(
module: &Module, module: &Module,
data_initializers: &[DataInitializer], ) -> Result<BoxedSlice<DefinedMemoryIndex, LinearMemory>, InstantiationError> {
) -> Result<BoxedSlice<DefinedMemoryIndex, LinearMemory>, String> {
let num_imports = module.imported_memories.len(); let num_imports = module.imported_memories.len();
let mut memories: PrimaryMap<DefinedMemoryIndex, _> = let mut memories: PrimaryMap<DefinedMemoryIndex, _> =
PrimaryMap::with_capacity(module.memory_plans.len() - num_imports); PrimaryMap::with_capacity(module.memory_plans.len() - num_imports);
for plan in &module.memory_plans.values().as_slice()[num_imports..] { for plan in &module.memory_plans.values().as_slice()[num_imports..] {
memories.push(LinearMemory::new(&plan)?); memories.push(LinearMemory::new(&plan).map_err(InstantiationError::Resource)?);
} }
for init in data_initializers {
debug_assert!(init.base.is_none(), "globalvar base not supported yet");
let defined_memory_index = module
.defined_memory_index(init.memory_index)
.expect("Initializers for imported memories not supported yet");
let mem_mut = memories[defined_memory_index].as_mut();
let to_init = &mut mem_mut[init.offset..init.offset + init.data.len()];
to_init.copy_from_slice(init.data);
}
Ok(memories.into_boxed_slice()) Ok(memories.into_boxed_slice())
} }
/// Initialize the table memory from the provided initializers.
fn initialize_memories(
instance: &mut Instance,
data_initializers: Vec<DataInitializer>,
) -> Result<(), InstantiationError> {
for init in data_initializers {
let mut start = init.offset;
if let Some(base) = init.base {
let global = if let Some(def_index) = instance.module.defined_global_index(base) {
unsafe { instance.vmctx.global_mut(def_index) }
} else {
unsafe { instance.vmctx.imported_global(base).from }
};
start += unsafe { *(&*global).as_i32() } as u32 as usize;
}
let memory = if let Some(defined_memory_index) =
instance.module.defined_memory_index(init.memory_index)
{
unsafe { instance.vmctx.memory(defined_memory_index) }
} else {
let import = &instance.vmctx_imports.memories[init.memory_index];
let foreign_instance = unsafe { (&mut *(import).vmctx).instance() };
let foreign_memory = unsafe { &mut *(import).from };
let foreign_index = foreign_instance.vmctx().memory_index(foreign_memory);
unsafe { foreign_instance.vmctx.memory(foreign_index) }
};
let mem_slice = unsafe { slice::from_raw_parts_mut(memory.base, memory.current_length) };
if let Some(to_init) = mem_slice.get_mut(start..start + init.data.len()) {
to_init.copy_from_slice(init.data);
} else {
return Err(InstantiationError::Link(
"data segment does not fit".to_owned(),
));
}
}
Ok(())
}
/// Allocate memory for just the globals of the current module, /// Allocate memory for just the globals of the current module,
/// without any initializers applied yet. /// with initializers applied.
fn instantiate_globals(module: &Module) -> BoxedSlice<DefinedGlobalIndex, VMGlobalDefinition> { fn create_globals(module: &Module) -> BoxedSlice<DefinedGlobalIndex, VMGlobalDefinition> {
let num_imports = module.imported_globals.len(); let num_imports = module.imported_globals.len();
let mut vmctx_globals = PrimaryMap::with_capacity(module.globals.len() - num_imports); let mut vmctx_globals = PrimaryMap::with_capacity(module.globals.len() - num_imports);
for global in &module.globals.values().as_slice()[num_imports..] { for _ in &module.globals.values().as_slice()[num_imports..] {
vmctx_globals.push(VMGlobalDefinition::new(global)); vmctx_globals.push(VMGlobalDefinition::new());
} }
vmctx_globals.into_boxed_slice() vmctx_globals.into_boxed_slice()
} }
fn initialize_globals(instance: &mut Instance) {
let num_imports = instance.module.imported_globals.len();
for (index, global) in instance.module.globals.iter().skip(num_imports) {
let def_index = instance.module.defined_global_index(index).unwrap();
let to: *mut VMGlobalDefinition = unsafe { instance.vmctx.global_mut(def_index) };
match global.initializer {
GlobalInit::I32Const(x) => *unsafe { (*to).as_i32_mut() } = x,
GlobalInit::I64Const(x) => *unsafe { (*to).as_i64_mut() } = x,
GlobalInit::F32Const(x) => *unsafe { (*to).as_f32_bits_mut() } = x,
GlobalInit::F64Const(x) => *unsafe { (*to).as_f64_bits_mut() } = x,
GlobalInit::GetGlobal(x) => {
let from = if let Some(def_x) = instance.module.defined_global_index(x) {
unsafe { instance.vmctx.global_mut(def_x) }
} else {
unsafe { instance.vmctx.imported_global(x).from }
};
unsafe { *to = *from };
}
GlobalInit::Import => panic!("locally-defined global initialized as import"),
}
}
}
/// An error while instantiating a module.
#[derive(Fail, Debug)]
pub enum InstantiationError {
/// Insufficient resources available for execution.
#[fail(display = "Insufficient resources: {}", _0)]
Resource(String),
/// A wasm translation error occured.
#[fail(display = "Link error: {}", _0)]
Link(String),
/// A compilation error occured.
#[fail(display = "Trap occurred while invoking start function: {}", _0)]
StartTrap(String),
}

View File

@@ -39,7 +39,11 @@ extern crate libc;
#[macro_use] #[macro_use]
extern crate memoffset; extern crate memoffset;
extern crate cast; extern crate cast;
extern crate failure;
#[macro_use]
extern crate failure_derive;
mod export;
mod imports; mod imports;
mod instance; mod instance;
mod memory; mod memory;
@@ -52,14 +56,15 @@ mod vmcontext;
pub mod libcalls; pub mod libcalls;
pub use export::Export;
pub use imports::Imports; pub use imports::Imports;
pub use instance::Instance; pub use instance::{Instance, InstantiationError};
pub use mmap::Mmap; pub use mmap::Mmap;
pub use signalhandlers::{wasmtime_init_eager, wasmtime_init_finish}; pub use signalhandlers::{wasmtime_init_eager, wasmtime_init_finish};
pub use traphandlers::wasmtime_call_trampoline; pub use traphandlers::{wasmtime_call, wasmtime_call_trampoline};
pub use vmcontext::{ pub use vmcontext::{
VMContext, VMFunctionBody, VMGlobalDefinition, VMGlobalImport, VMMemoryDefinition, VMContext, VMFunctionBody, VMFunctionImport, VMGlobalDefinition, VMGlobalImport,
VMMemoryImport, VMTableDefinition, VMTableImport, VMMemoryDefinition, VMMemoryImport, VMTableDefinition, VMTableImport,
}; };
#[cfg(not(feature = "std"))] #[cfg(not(feature = "std"))]

View File

@@ -116,7 +116,7 @@ pub unsafe extern "C" fn wasmtime_imported_memory32_grow(
); );
let memory_index = MemoryIndex::from_u32(memory_index); let memory_index = MemoryIndex::from_u32(memory_index);
let import = instance.vmctx_mut().imported_memory_mut(memory_index); let import = instance.vmctx().imported_memory(memory_index);
let foreign_instance = (&mut *import.vmctx).instance(); let foreign_instance = (&mut *import.vmctx).instance();
let foreign_memory = &mut *import.from; let foreign_memory = &mut *import.from;
let foreign_index = foreign_instance.vmctx().memory_index(foreign_memory); let foreign_index = foreign_instance.vmctx().memory_index(foreign_memory);
@@ -148,7 +148,7 @@ pub unsafe extern "C" fn wasmtime_imported_memory32_size(
); );
let memory_index = MemoryIndex::from_u32(memory_index); let memory_index = MemoryIndex::from_u32(memory_index);
let import = instance.vmctx_mut().imported_memory_mut(memory_index); let import = instance.vmctx().imported_memory(memory_index);
let foreign_instance = (&mut *import.vmctx).instance(); let foreign_instance = (&mut *import.vmctx).instance();
let foreign_memory = &mut *import.from; let foreign_memory = &mut *import.from;
let foreign_index = foreign_instance.vmctx().memory_index(foreign_memory); let foreign_index = foreign_instance.vmctx().memory_index(foreign_memory);

View File

@@ -65,6 +65,7 @@ impl LinearMemory {
let mmap = Mmap::with_size(request_bytes)?; let mmap = Mmap::with_size(request_bytes)?;
// Make the unmapped and offset-guard pages inaccessible. // Make the unmapped and offset-guard pages inaccessible.
if request_bytes != 0 {
unsafe { unsafe {
region::protect( region::protect(
mmap.as_ptr().add(mapped_bytes), mmap.as_ptr().add(mapped_bytes),
@@ -73,6 +74,7 @@ impl LinearMemory {
) )
} }
.expect("unable to make memory inaccessible"); .expect("unable to make memory inaccessible");
}
Ok(Self { Ok(Self {
mmap, mmap,
@@ -150,19 +152,7 @@ impl LinearMemory {
pub fn vmmemory(&mut self) -> VMMemoryDefinition { pub fn vmmemory(&mut self) -> VMMemoryDefinition {
VMMemoryDefinition { VMMemoryDefinition {
base: self.mmap.as_mut_ptr(), base: self.mmap.as_mut_ptr(),
current_length: self.mmap.len(), current_length: self.current as usize * WASM_PAGE_SIZE as usize,
} }
} }
} }
impl AsRef<[u8]> for LinearMemory {
fn as_ref(&self) -> &[u8] {
self.mmap.as_slice()
}
}
impl AsMut<[u8]> for LinearMemory {
fn as_mut(&mut self) -> &mut [u8] {
self.mmap.as_mut_slice()
}
}

View File

@@ -34,6 +34,12 @@ impl Mmap {
/// suitably sized and aligned for memory protection. /// suitably sized and aligned for memory protection.
#[cfg(not(target_os = "windows"))] #[cfg(not(target_os = "windows"))]
pub fn with_size(size: usize) -> Result<Self, String> { pub fn with_size(size: usize) -> Result<Self, String> {
// Mmap may return EINVAL if the size is zero, so just
// special-case that.
if size == 0 {
return Ok(Self::new());
}
let page_size = region::page::size(); let page_size = region::page::size();
let alloc_size = round_up_to_page_size(size, page_size); let alloc_size = round_up_to_page_size(size, page_size);
let ptr = unsafe { let ptr = unsafe {

View File

@@ -107,3 +107,27 @@ pub unsafe extern "C" fn wasmtime_call_trampoline(
Ok(()) Ok(())
}) })
} }
/// Call the wasm function pointed to by `callee`, which has no arguments or
/// return values.
#[no_mangle]
pub unsafe extern "C" fn wasmtime_call(
callee: *const VMFunctionBody,
vmctx: *mut VMContext,
) -> Result<(), String> {
// In case wasm code calls Rust that panics and unwinds past this point,
// ensure that JMP_BUFS is unwound to its incoming state.
let _guard = ScopeGuard::new();
let func: fn(*mut VMContext) = mem::transmute(callee);
JMP_BUFS.with(|bufs| {
let mut buf = mem::uninitialized();
if setjmp(&mut buf) != 0 {
return TRAP_DATA.with(|data| Err(format!("wasm trap at {:?}", data.get().pc)));
}
bufs.borrow_mut().push(buf);
func(vmctx);
Ok(())
})
}

View File

@@ -3,12 +3,47 @@
use cranelift_entity::EntityRef; use cranelift_entity::EntityRef;
use cranelift_wasm::{ use cranelift_wasm::{
DefinedGlobalIndex, DefinedMemoryIndex, DefinedTableIndex, FuncIndex, Global, GlobalIndex, DefinedGlobalIndex, DefinedMemoryIndex, DefinedTableIndex, FuncIndex, GlobalIndex, MemoryIndex,
GlobalInit, MemoryIndex, TableIndex, TableIndex,
}; };
use instance::Instance; use instance::Instance;
use std::{mem, ptr, u32}; use std::{mem, ptr, u32};
/// An imported function.
#[derive(Debug, Copy, Clone)]
#[repr(C)]
pub struct VMFunctionImport {
/// A pointer to the imported function body.
pub body: *const VMFunctionBody,
/// A pointer to the VMContext that owns the function.
pub vmctx: *mut VMContext,
}
#[cfg(test)]
mod test_vmfunction_import {
use super::VMFunctionImport;
use std::mem::size_of;
use wasmtime_environ::VMOffsets;
#[test]
fn check_vmfunction_import_offsets() {
let offsets = VMOffsets::new(size_of::<*mut u8>() as u8);
assert_eq!(
size_of::<VMFunctionImport>(),
usize::from(offsets.size_of_vmfunction_import())
);
assert_eq!(
offset_of!(VMFunctionImport, body),
usize::from(offsets.vmfunction_import_body())
);
assert_eq!(
offset_of!(VMFunctionImport, vmctx),
usize::from(offsets.vmfunction_import_vmctx())
);
}
}
/// A placeholder byte-sized type which is just used to provide some amount of type /// A placeholder byte-sized type which is just used to provide some amount of type
/// safety when dealing with pointers to JIT-compiled function bodies. Note that it's /// safety when dealing with pointers to JIT-compiled function bodies. Note that it's
/// deliberately not Copy, as we shouldn't be carelessly copying function body bytes /// deliberately not Copy, as we shouldn't be carelessly copying function body bytes
@@ -244,17 +279,8 @@ mod test_vmglobal_definition {
impl VMGlobalDefinition { impl VMGlobalDefinition {
/// Construct a `VMGlobalDefinition`. /// Construct a `VMGlobalDefinition`.
pub fn new(global: &Global) -> Self { pub fn new() -> Self {
let mut result = Self { storage: [0; 8] }; Self { storage: [0; 8] }
match global.initializer {
GlobalInit::I32Const(x) => *unsafe { result.as_i32_mut() } = x,
GlobalInit::I64Const(x) => *unsafe { result.as_i64_mut() } = x,
GlobalInit::F32Const(x) => *unsafe { result.as_f32_bits_mut() } = x,
GlobalInit::F64Const(x) => *unsafe { result.as_f64_bits_mut() } = x,
GlobalInit::GetGlobal(_x) => unimplemented!("globals init with get_global"),
GlobalInit::Import => panic!("attempting to initialize imported global"),
}
result
} }
/// Return a reference to the value as an i32. /// Return a reference to the value as an i32.
@@ -366,6 +392,7 @@ impl VMSharedSignatureIndex {
pub struct VMCallerCheckedAnyfunc { pub struct VMCallerCheckedAnyfunc {
pub func_ptr: *const VMFunctionBody, pub func_ptr: *const VMFunctionBody,
pub type_index: VMSharedSignatureIndex, pub type_index: VMSharedSignatureIndex,
pub vmctx: *mut VMContext,
// If more elements are added here, remember to add offset_of tests below! // If more elements are added here, remember to add offset_of tests below!
} }
@@ -390,6 +417,10 @@ mod test_vmcaller_checked_anyfunc {
offset_of!(VMCallerCheckedAnyfunc, type_index), offset_of!(VMCallerCheckedAnyfunc, type_index),
usize::from(offsets.vmcaller_checked_anyfunc_type_index()) usize::from(offsets.vmcaller_checked_anyfunc_type_index())
); );
assert_eq!(
offset_of!(VMCallerCheckedAnyfunc, vmctx),
usize::from(offsets.vmcaller_checked_anyfunc_vmctx())
);
} }
} }
@@ -398,6 +429,7 @@ impl Default for VMCallerCheckedAnyfunc {
Self { Self {
func_ptr: ptr::null_mut(), func_ptr: ptr::null_mut(),
type_index: VMSharedSignatureIndex::new(u32::MAX), type_index: VMSharedSignatureIndex::new(u32::MAX),
vmctx: ptr::null_mut(),
} }
} }
} }
@@ -413,16 +445,16 @@ impl Default for VMCallerCheckedAnyfunc {
#[repr(C)] #[repr(C)]
pub struct VMContext { pub struct VMContext {
/// A pointer to an array of `*const VMFunctionBody` instances, indexed by `FuncIndex`. /// A pointer to an array of `*const VMFunctionBody` instances, indexed by `FuncIndex`.
imported_functions: *const *const VMFunctionBody, imported_functions: *const VMFunctionImport,
/// A pointer to an array of `VMTableImport` instances, indexed by `TableIndex`. /// A pointer to an array of `VMTableImport` instances, indexed by `TableIndex`.
imported_tables: *mut VMTableImport, imported_tables: *const VMTableImport,
/// A pointer to an array of `VMMemoryImport` instances, indexed by `MemoryIndex`. /// A pointer to an array of `VMMemoryImport` instances, indexed by `MemoryIndex`.
imported_memories: *mut VMMemoryImport, imported_memories: *const VMMemoryImport,
/// A pointer to an array of `VMGlobalImport` instances, indexed by `GlobalIndex`. /// A pointer to an array of `VMGlobalImport` instances, indexed by `GlobalIndex`.
imported_globals: *mut VMGlobalImport, imported_globals: *const VMGlobalImport,
/// A pointer to an array of locally-defined `VMTableDefinition` instances, /// A pointer to an array of locally-defined `VMTableDefinition` instances,
/// indexed by `DefinedTableIndex`. /// indexed by `DefinedTableIndex`.
@@ -473,10 +505,10 @@ mod test {
impl VMContext { impl VMContext {
/// Create a new `VMContext` instance. /// Create a new `VMContext` instance.
pub fn new( pub fn new(
imported_functions: *const *const VMFunctionBody, imported_functions: *const VMFunctionImport,
imported_tables: *mut VMTableImport, imported_tables: *const VMTableImport,
imported_memories: *mut VMMemoryImport, imported_memories: *const VMMemoryImport,
imported_globals: *mut VMGlobalImport, imported_globals: *const VMGlobalImport,
tables: *mut VMTableDefinition, tables: *mut VMTableDefinition,
memories: *mut VMMemoryDefinition, memories: *mut VMMemoryDefinition,
globals: *mut VMGlobalDefinition, globals: *mut VMGlobalDefinition,
@@ -495,8 +527,8 @@ impl VMContext {
} }
/// Return a reference to imported function `index`. /// Return a reference to imported function `index`.
pub unsafe fn imported_function(&self, index: FuncIndex) -> *const VMFunctionBody { pub unsafe fn imported_function(&self, index: FuncIndex) -> &VMFunctionImport {
*self.imported_functions.add(index.index()) &*self.imported_functions.add(index.index())
} }
/// Return a reference to imported table `index`. /// Return a reference to imported table `index`.
@@ -504,31 +536,16 @@ impl VMContext {
&*self.imported_tables.add(index.index()) &*self.imported_tables.add(index.index())
} }
/// Return a mutable reference to imported table `index`.
pub unsafe fn imported_table_mut(&mut self, index: TableIndex) -> &mut VMTableImport {
&mut *self.imported_tables.add(index.index())
}
/// Return a reference to imported memory `index`. /// Return a reference to imported memory `index`.
pub unsafe fn imported_memory(&self, index: MemoryIndex) -> &VMMemoryImport { pub unsafe fn imported_memory(&self, index: MemoryIndex) -> &VMMemoryImport {
&*self.imported_memories.add(index.index()) &*self.imported_memories.add(index.index())
} }
/// Return a mutable reference to imported memory `index`.
pub unsafe fn imported_memory_mut(&mut self, index: MemoryIndex) -> &mut VMMemoryImport {
&mut *self.imported_memories.add(index.index())
}
/// Return a reference to imported global `index`. /// Return a reference to imported global `index`.
pub unsafe fn imported_global(&self, index: GlobalIndex) -> &VMGlobalImport { pub unsafe fn imported_global(&self, index: GlobalIndex) -> &VMGlobalImport {
&*self.imported_globals.add(index.index()) &*self.imported_globals.add(index.index())
} }
/// Return a mutable reference to imported global `index`.
pub unsafe fn imported_global_mut(&mut self, index: GlobalIndex) -> &mut VMGlobalImport {
&mut *self.imported_globals.add(index.index())
}
/// Return a reference to locally-defined table `index`. /// Return a reference to locally-defined table `index`.
pub unsafe fn table(&self, index: DefinedTableIndex) -> &VMTableDefinition { pub unsafe fn table(&self, index: DefinedTableIndex) -> &VMTableDefinition {
&*self.tables.add(index.index()) &*self.tables.add(index.index())
@@ -565,6 +582,16 @@ impl VMContext {
&mut *((self as *mut Self as *mut u8).offset(-Instance::vmctx_offset()) as *mut Instance) &mut *((self as *mut Self as *mut u8).offset(-Instance::vmctx_offset()) as *mut Instance)
} }
/// Return the table index for the given `VMTableDefinition`.
pub fn table_index(&self, table: &mut VMTableDefinition) -> DefinedTableIndex {
// TODO: Use `offset_from` once it stablizes.
let begin = self.tables;
let end: *mut VMTableDefinition = table;
DefinedTableIndex::new(
(end as usize - begin as usize) / mem::size_of::<VMTableDefinition>(),
)
}
/// Return the memory index for the given `VMMemoryDefinition`. /// Return the memory index for the given `VMMemoryDefinition`.
pub fn memory_index(&self, memory: &mut VMMemoryDefinition) -> DefinedMemoryIndex { pub fn memory_index(&self, memory: &mut VMMemoryDefinition) -> DefinedMemoryIndex {
// TODO: Use `offset_from` once it stablizes. // TODO: Use `offset_from` once it stablizes.

View File

@@ -1,103 +0,0 @@
use std::env;
use std::fs::{read_dir, File};
use std::io::{self, Write};
use std::path::{Path, PathBuf};
fn main() {
let out_dir =
PathBuf::from(env::var("OUT_DIR").expect("The OUT_DIR environment variable must be set"));
let mut out =
File::create(out_dir.join("run_wast_files.rs")).expect("error creating run_wast_files.rs");
test_directory(&mut out, "misc_testsuite").unwrap();
test_directory(&mut out, "spec_testsuite").unwrap();
}
fn test_directory(out: &mut File, testsuite: &str) -> io::Result<()> {
let mut dir_entries: Vec<_> = read_dir(testsuite)
.unwrap()
.map(|r| r.unwrap())
.filter(|dir_entry| {
let p = dir_entry.path();
if let Some(ext) = p.extension() {
// Only look at wast files.
if ext == "wast" {
// Ignore files starting with `.`, which could be editor temporary files
if let Some(stem) = p.file_stem() {
if let Some(stemstr) = stem.to_str() {
if !stemstr.starts_with('.') {
return true;
}
}
}
}
}
false
})
.collect();
dir_entries.sort_by_key(|dir| dir.path());
writeln!(
out,
"mod {} {{",
Path::new(testsuite)
.file_stem()
.unwrap()
.to_str()
.unwrap()
.replace("-", "_")
)?;
writeln!(out, " use super::{{native_isa, WastContext, Path}};")?;
for dir_entry in dir_entries {
let path = dir_entry.path();
let stemstr = path
.file_stem()
.expect("file_stem")
.to_str()
.expect("to_str");
writeln!(out, " #[test]")?;
if ignore(testsuite, stemstr) {
writeln!(out, " #[ignore]")?;
}
writeln!(
out,
" fn {}() {{",
avoid_keywords(&stemstr.replace("-", "_"))
)?;
writeln!(out, " let mut wast_context = WastContext::new().expect(\"error constructing WastContext\");")?;
writeln!(
out,
" wast_context.run_file(&*native_isa(), Path::new(\"{}\")).expect(\"error running wast file: {}\");",
path.display(),
path.display()
)?;
writeln!(out, " }}")?;
writeln!(out)?;
}
writeln!(out, "}}")?;
Ok(())
}
fn avoid_keywords(name: &str) -> &str {
match name {
"if" => "if_",
"loop" => "loop_",
"type" => "type_",
"const" => "const_",
"return" => "return_",
other => other,
}
}
fn ignore(testsuite: &str, name: &str) -> bool {
match testsuite {
"spec_testsuite" => match name {
// These are the remaining spec testsuite failures.
"data" | "elem" | "imports" | "linking" => true,
_ => false,
},
_ => false,
}
}

View File

@@ -1,16 +1,14 @@
use cranelift_codegen::ir::types; use cranelift_codegen::ir::types;
use cranelift_codegen::{ir, isa}; use cranelift_codegen::{ir, isa};
use cranelift_entity::PrimaryMap; use cranelift_entity::PrimaryMap;
use cranelift_wasm::{Global, GlobalInit, Memory, Table, TableElementType}; use cranelift_wasm::{DefinedFuncIndex, Global, GlobalInit, Memory, Table, TableElementType};
use std::ptr; use std::rc::Rc;
use target_lexicon::HOST; use target_lexicon::HOST;
use wasmtime_environ::{ use wasmtime_environ::{
translate_signature, MemoryPlan, MemoryStyle, Module, TablePlan, TableStyle, translate_signature, Export, MemoryPlan, MemoryStyle, Module, TablePlan, TableStyle,
};
use wasmtime_execute::{Export, Resolver};
use wasmtime_runtime::{
Imports, Instance, VMFunctionBody, VMGlobalDefinition, VMMemoryDefinition, VMTableDefinition,
}; };
use wasmtime_execute::{ActionError, InstancePlus};
use wasmtime_runtime::{Imports, VMFunctionBody};
extern "C" fn spectest_print() {} extern "C" fn spectest_print() {}
@@ -46,195 +44,181 @@ extern "C" fn spectest_print_f64_f64(x: f64, y: f64) {
println!("{}: f64", y); println!("{}: f64", y);
} }
pub struct SpecTest { /// Return an instance implementing the "spectest" interface used in the
instance: Instance, /// spec testsuite.
spectest_global_i32: VMGlobalDefinition, pub fn instantiate_spectest() -> Result<InstancePlus, ActionError> {
spectest_global_f32: VMGlobalDefinition,
spectest_global_f64: VMGlobalDefinition,
spectest_table: VMTableDefinition,
spectest_memory: VMMemoryDefinition,
}
impl SpecTest {
pub fn new() -> Result<Self, String> {
let finished_functions = PrimaryMap::new();
let imports = Imports::none();
let data_initializers = Vec::new();
Ok(Self {
instance: Instance::new(
&Module::new(),
&finished_functions.into_boxed_slice(),
imports,
&data_initializers,
)?,
spectest_global_i32: VMGlobalDefinition::new(&Global {
ty: types::I32,
mutability: true,
initializer: GlobalInit::I32Const(0),
}),
spectest_global_f32: VMGlobalDefinition::new(&Global {
ty: types::I32,
mutability: true,
initializer: GlobalInit::F32Const(0),
}),
spectest_global_f64: VMGlobalDefinition::new(&Global {
ty: types::I32,
mutability: true,
initializer: GlobalInit::F64Const(0),
}),
spectest_table: VMTableDefinition {
base: ptr::null_mut(),
current_elements: 0,
},
spectest_memory: VMMemoryDefinition {
base: ptr::null_mut(),
current_length: 0,
},
})
}
}
impl Resolver for SpecTest {
fn resolve(&mut self, module: &str, field: &str) -> Option<Export> {
let call_conv = isa::CallConv::triple_default(&HOST); let call_conv = isa::CallConv::triple_default(&HOST);
let pointer_type = types::Type::triple_pointer_type(&HOST); let pointer_type = types::Type::triple_pointer_type(&HOST);
match module { let mut module = Module::new();
"spectest" => match field { let mut finished_functions: PrimaryMap<DefinedFuncIndex, *const VMFunctionBody> =
"print" => Some(Export::function( PrimaryMap::new();
spectest_print as *const VMFunctionBody,
translate_signature( let sig = module.signatures.push(translate_signature(
ir::Signature { ir::Signature {
params: vec![], params: vec![],
returns: vec![], returns: vec![],
call_conv, call_conv,
}, },
pointer_type, pointer_type,
), ));
)), let func = module.functions.push(sig);
"print_i32" => Some(Export::function( module
spectest_print_i32 as *const VMFunctionBody, .exports
translate_signature( .insert("print".to_owned(), Export::Function(func));
finished_functions.push(spectest_print as *const VMFunctionBody);
let sig = module.signatures.push(translate_signature(
ir::Signature { ir::Signature {
params: vec![ir::AbiParam::new(types::I32)], params: vec![ir::AbiParam::new(types::I32)],
returns: vec![], returns: vec![],
call_conv, call_conv,
}, },
pointer_type, pointer_type,
), ));
)), let func = module.functions.push(sig);
"print_i64" => Some(Export::function( module
spectest_print_i64 as *const VMFunctionBody, .exports
translate_signature( .insert("print_i32".to_owned(), Export::Function(func));
finished_functions.push(spectest_print_i32 as *const VMFunctionBody);
let sig = module.signatures.push(translate_signature(
ir::Signature { ir::Signature {
params: vec![ir::AbiParam::new(types::I64)], params: vec![ir::AbiParam::new(types::I64)],
returns: vec![], returns: vec![],
call_conv, call_conv,
}, },
pointer_type, pointer_type,
), ));
)), let func = module.functions.push(sig);
"print_f32" => Some(Export::function( module
spectest_print_f32 as *const VMFunctionBody, .exports
translate_signature( .insert("print_i64".to_owned(), Export::Function(func));
finished_functions.push(spectest_print_i64 as *const VMFunctionBody);
let sig = module.signatures.push(translate_signature(
ir::Signature { ir::Signature {
params: vec![ir::AbiParam::new(types::F32)], params: vec![ir::AbiParam::new(types::F32)],
returns: vec![], returns: vec![],
call_conv, call_conv,
}, },
pointer_type, pointer_type,
), ));
)), let func = module.functions.push(sig);
"print_f64" => Some(Export::function( module
spectest_print_f64 as *const VMFunctionBody, .exports
translate_signature( .insert("print_f32".to_owned(), Export::Function(func));
finished_functions.push(spectest_print_f32 as *const VMFunctionBody);
let sig = module.signatures.push(translate_signature(
ir::Signature { ir::Signature {
params: vec![ir::AbiParam::new(types::F64)], params: vec![ir::AbiParam::new(types::F64)],
returns: vec![], returns: vec![],
call_conv, call_conv,
}, },
pointer_type, pointer_type,
), ));
)), let func = module.functions.push(sig);
"print_i32_f32" => Some(Export::function( module
spectest_print_i32_f32 as *const VMFunctionBody, .exports
translate_signature( .insert("print_f64".to_owned(), Export::Function(func));
finished_functions.push(spectest_print_f64 as *const VMFunctionBody);
let sig = module.signatures.push(translate_signature(
ir::Signature { ir::Signature {
params: vec![ params: vec![ir::AbiParam::new(types::I32), ir::AbiParam::new(types::F32)],
ir::AbiParam::new(types::I32),
ir::AbiParam::new(types::F32),
],
returns: vec![], returns: vec![],
call_conv, call_conv,
}, },
pointer_type, pointer_type,
), ));
)), let func = module.functions.push(sig);
"print_f64_f64" => Some(Export::function( module
spectest_print_f64_f64 as *const VMFunctionBody, .exports
translate_signature( .insert("print_i32_f32".to_owned(), Export::Function(func));
finished_functions.push(spectest_print_i32_f32 as *const VMFunctionBody);
let sig = module.signatures.push(translate_signature(
ir::Signature { ir::Signature {
params: vec![ params: vec![ir::AbiParam::new(types::F64), ir::AbiParam::new(types::F64)],
ir::AbiParam::new(types::F64),
ir::AbiParam::new(types::F64),
],
returns: vec![], returns: vec![],
call_conv, call_conv,
}, },
pointer_type, pointer_type,
), ));
)), let func = module.functions.push(sig);
"global_i32" => Some(Export::global( module
&mut self.spectest_global_i32, .exports
Global { .insert("print_f64_f64".to_owned(), Export::Function(func));
ty: ir::types::I32, finished_functions.push(spectest_print_f64_f64 as *const VMFunctionBody);
let global = module.globals.push(Global {
ty: types::I32,
mutability: false, mutability: false,
initializer: GlobalInit::I32Const(0), initializer: GlobalInit::I32Const(666),
}, });
)), module
"global_f32" => Some(Export::global( .exports
&mut self.spectest_global_f32, .insert("global_i32".to_owned(), Export::Global(global));
Global {
ty: ir::types::F32, let global = module.globals.push(Global {
ty: types::I64,
mutability: false, mutability: false,
initializer: GlobalInit::F32Const(0), initializer: GlobalInit::I64Const(666),
}, });
)), module
"global_f64" => Some(Export::global( .exports
&mut self.spectest_global_f64, .insert("global_i64".to_owned(), Export::Global(global));
Global {
ty: ir::types::F64, let global = module.globals.push(Global {
ty: types::F32,
mutability: false, mutability: false,
initializer: GlobalInit::F64Const(0), initializer: GlobalInit::F32Const(0x44268000),
}, });
)), module
"table" => Some(Export::table( .exports
&mut self.spectest_table, .insert("global_f32".to_owned(), Export::Global(global));
self.instance.vmctx_mut(),
TablePlan { let global = module.globals.push(Global {
ty: types::F64,
mutability: false,
initializer: GlobalInit::F64Const(0x4084d00000000000),
});
module
.exports
.insert("global_f64".to_owned(), Export::Global(global));
let table = module.table_plans.push(TablePlan {
table: Table { table: Table {
ty: TableElementType::Func, ty: TableElementType::Func,
minimum: 0, minimum: 10,
maximum: None, maximum: Some(20),
}, },
style: TableStyle::CallerChecksSignature, style: TableStyle::CallerChecksSignature,
}, });
)), module
"memory" => Some(Export::memory( .exports
&mut self.spectest_memory, .insert("table".to_owned(), Export::Table(table));
self.instance.vmctx_mut(),
MemoryPlan { let memory = module.memory_plans.push(MemoryPlan {
memory: Memory { memory: Memory {
minimum: 0, minimum: 1,
maximum: None, maximum: Some(2),
shared: false, shared: false,
}, },
style: MemoryStyle::Dynamic, style: MemoryStyle::Static { bound: 65536 },
offset_guard_size: 0, offset_guard_size: 0x80000000,
}, });
)), module
_ => None, .exports
}, .insert("memory".to_owned(), Export::Memory(memory));
_ => None,
} let imports = Imports::none();
} let data_initializers = Vec::new();
InstancePlus::with_parts(
Rc::new(module),
finished_functions.into_boxed_slice(),
imports,
data_initializers,
)
} }

View File

@@ -1,12 +1,13 @@
use cranelift_codegen::isa; use cranelift_codegen::isa;
use cranelift_entity::PrimaryMap; use cranelift_entity::PrimaryMap;
use spectest::SpecTest; use spectest::instantiate_spectest;
use std::collections::HashMap; use std::collections::HashMap;
use std::io::Read; use std::io::Read;
use std::path::Path; use std::path::Path;
use std::{fmt, fs, io, str}; use std::{fmt, fs, io, str};
use wabt::script::{Action, Command, CommandKind, ModuleBinary, ScriptParser, Value}; use wabt::script::{Action, Command, CommandKind, ModuleBinary, ScriptParser, Value};
use wasmtime_execute::{ActionError, ActionOutcome, Code, InstanceWorld, RuntimeValue}; use wasmtime_execute::{ActionError, ActionOutcome, InstancePlus, JITCode, Resolver, RuntimeValue};
use wasmtime_runtime::Export;
/// Translate from a script::Value to a RuntimeValue. /// Translate from a script::Value to a RuntimeValue.
fn runtime_value(v: Value) -> RuntimeValue { fn runtime_value(v: Value) -> RuntimeValue {
@@ -72,45 +73,70 @@ pub struct WastFileError {
error: WastError, error: WastError,
} }
/// An opaque reference to an `InstanceWorld`. /// An opaque reference to an `InstancePlus`.
#[derive(Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)] #[derive(Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct WorldIndex(u32); pub struct InstancePlusIndex(u32);
entity_impl!(WorldIndex, "world"); entity_impl!(InstancePlusIndex, "instance");
struct WasmNamespace {
names: HashMap<String, InstancePlusIndex>,
instances: PrimaryMap<InstancePlusIndex, InstancePlus>,
}
impl WasmNamespace {
fn new() -> Self {
Self {
names: HashMap::new(),
instances: PrimaryMap::new(),
}
}
}
impl Resolver for WasmNamespace {
fn resolve(&mut self, module: &str, field: &str) -> Option<Export> {
if let Some(index) = self.names.get(module) {
self.instances[*index].instance.lookup(field)
} else {
None
}
}
}
/// The wast test script language allows modules to be defined and actions /// The wast test script language allows modules to be defined and actions
/// to be performed on them. /// to be performed on them.
pub struct WastContext { pub struct WastContext {
/// A namespace of wasm modules, keyed by an optional name. /// A namespace of wasm modules, keyed by an optional name.
worlds: PrimaryMap<WorldIndex, InstanceWorld>, current: Option<InstancePlusIndex>,
current: Option<WorldIndex>, namespace: WasmNamespace,
namespace: HashMap<String, WorldIndex>, jit_code: JITCode,
code: Code,
spectest: SpecTest,
} }
impl WastContext { impl WastContext {
/// Construct a new instance of `WastContext`. /// Construct a new instance of `WastContext`.
pub fn new() -> Result<Self, String> { pub fn new() -> Self {
Ok(Self { Self {
worlds: PrimaryMap::new(),
current: None, current: None,
namespace: HashMap::new(), namespace: WasmNamespace::new(),
code: Code::new(), jit_code: JITCode::new(),
spectest: SpecTest::new()?, }
})
} }
fn instantiate( fn instantiate(
&mut self, &mut self,
isa: &isa::TargetIsa, isa: &isa::TargetIsa,
module: ModuleBinary, module: ModuleBinary,
) -> Result<InstanceWorld, ActionError> { ) -> Result<InstancePlus, ActionError> {
InstanceWorld::new(&mut self.code, isa, &module.into_vec(), &mut self.spectest) InstancePlus::new(
&mut self.jit_code,
isa,
&module.into_vec(),
&mut self.namespace,
)
} }
fn get_world(&mut self, module: &Option<String>) -> Result<WorldIndex, WastError> { fn get_instance(&mut self, module: &Option<String>) -> Result<InstancePlusIndex, WastError> {
let index = *if let Some(name) = module { let index = *if let Some(name) = module {
self.namespace.get_mut(name).ok_or_else(|| { self.namespace.names.get_mut(name).ok_or_else(|| {
WastError::Module(UnknownModule { WastError::Module(UnknownModule {
module: Some(name.to_owned()), module: Some(name.to_owned()),
}) })
@@ -124,6 +150,14 @@ impl WastContext {
Ok(index) Ok(index)
} }
/// Register "spectest" which is used by the spec testsuite.
pub fn register_spectest(&mut self) -> Result<(), ActionError> {
let instance = instantiate_spectest()?;
let index = self.namespace.instances.push(instance);
self.register("spectest".to_owned(), index);
Ok(())
}
/// Define a module and register it. /// Define a module and register it.
pub fn module( pub fn module(
&mut self, &mut self,
@@ -131,21 +165,18 @@ impl WastContext {
name: Option<String>, name: Option<String>,
module: ModuleBinary, module: ModuleBinary,
) -> Result<(), ActionError> { ) -> Result<(), ActionError> {
let world = self.instantiate(isa, module)?; let instance = self.instantiate(isa, module)?;
let index = if let Some(name) = name { let index = self.namespace.instances.push(instance);
self.register(name, world) if let Some(name) = name {
} else { self.register(name, index);
self.worlds.push(world) }
};
self.current = Some(index); self.current = Some(index);
Ok(()) Ok(())
} }
/// Register a module to make it available for performing actions. /// Register a module to make it available for performing actions.
pub fn register(&mut self, name: String, world: InstanceWorld) -> WorldIndex { pub fn register(&mut self, name: String, index: InstancePlusIndex) {
let index = self.worlds.push(world); self.namespace.names.insert(name, index);
self.namespace.insert(name, index);
index
} }
/// Invoke an exported function from a defined module. /// Invoke an exported function from a defined module.
@@ -160,16 +191,18 @@ impl WastContext {
for arg in args { for arg in args {
value_args.push(runtime_value(*arg)); value_args.push(runtime_value(*arg));
} }
let index = self.get_world(&module)?; let index = self.get_instance(&module)?;
self.worlds[index] self.namespace.instances[index]
.invoke(&mut self.code, isa, &field, &value_args) .invoke(&mut self.jit_code, isa, &field, &value_args)
.map_err(WastError::Action) .map_err(WastError::Action)
} }
/// Get the value of an exported global from a defined module. /// Get the value of an exported global from a defined module.
pub fn get(&mut self, module: Option<String>, field: &str) -> Result<RuntimeValue, WastError> { pub fn get(&mut self, module: Option<String>, field: &str) -> Result<RuntimeValue, WastError> {
let index = self.get_world(&module)?; let index = self.get_instance(&module)?;
self.worlds[index].get(&field).map_err(WastError::Action) self.namespace.instances[index]
.get(&field)
.map_err(WastError::Action)
} }
fn perform_action( fn perform_action(
@@ -211,11 +244,13 @@ impl WastContext {
error: WastError::Action(error), error: WastError::Action(error),
})?; })?;
} }
CommandKind::Register { CommandKind::Register { name, as_name } => {
name: _name, let index = self.get_instance(&name).map_err(|error| WastFileError {
as_name: _as_name, filename: filename.to_string(),
} => { line,
println!("{}:{}: TODO: Implement register", filename, line); error,
})?;
self.register(as_name, index);
} }
CommandKind::PerformAction(action) => match self CommandKind::PerformAction(action) => match self
.perform_action(isa, action) .perform_action(isa, action)

View File

@@ -31,9 +31,7 @@
)] )]
extern crate cranelift_codegen; extern crate cranelift_codegen;
extern crate cranelift_entity;
extern crate cranelift_native; extern crate cranelift_native;
extern crate cranelift_wasm;
extern crate docopt; extern crate docopt;
extern crate wasmtime_execute; extern crate wasmtime_execute;
#[macro_use] #[macro_use]
@@ -45,18 +43,15 @@ extern crate wabt;
use cranelift_codegen::isa::TargetIsa; use cranelift_codegen::isa::TargetIsa;
use cranelift_codegen::settings; use cranelift_codegen::settings;
use cranelift_codegen::settings::Configurable; use cranelift_codegen::settings::Configurable;
use cranelift_entity::EntityRef;
use cranelift_wasm::MemoryIndex;
use docopt::Docopt; use docopt::Docopt;
use std::error::Error; use std::error::Error;
use std::fs::File; use std::fs::File;
use std::io; use std::io;
use std::io::prelude::*; use std::io::prelude::*;
use std::io::stdout;
use std::path::Path; use std::path::Path;
use std::path::PathBuf; use std::path::PathBuf;
use std::process::exit; use std::process::exit;
use wasmtime_execute::{ActionOutcome, Code, InstanceWorld, NullResolver}; use wasmtime_execute::{ActionOutcome, InstancePlus, JITCode, NullResolver};
static LOG_FILENAME_PREFIX: &str = "cranelift.dbg."; static LOG_FILENAME_PREFIX: &str = "cranelift.dbg.";
@@ -68,14 +63,13 @@ including calling the start function if one is present. Additional functions
given with --invoke are then called. given with --invoke are then called.
Usage: Usage:
wasmtime [-omd] <file>... wasmtime [-od] <file>...
wasmtime [-omd] <file>... --invoke=<fn> wasmtime [-od] <file>... --invoke=<fn>
wasmtime --help | --version wasmtime --help | --version
Options: Options:
--invoke=<fn> name of function to run --invoke=<fn> name of function to run
-o, --optimize runs optimization passes on the translated functions -o, --optimize runs optimization passes on the translated functions
-m, --memory interactive memory inspector after execution
-d, --debug enable debug output on stderr/stdout -d, --debug enable debug output on stderr/stdout
-h, --help print this help message -h, --help print this help message
--version print the Cranelift version --version print the Cranelift version
@@ -84,7 +78,6 @@ Options:
#[derive(Deserialize, Debug, Clone)] #[derive(Deserialize, Debug, Clone)]
struct Args { struct Args {
arg_file: Vec<String>, arg_file: Vec<String>,
flag_memory: bool,
flag_optimize: bool, flag_optimize: bool,
flag_debug: bool, flag_debug: bool,
flag_invoke: Option<String>, flag_invoke: Option<String>,
@@ -150,13 +143,13 @@ fn handle_module(args: &Args, path: &Path, isa: &TargetIsa) -> Result<(), String
data = wabt::wat2wasm(data).map_err(|err| String::from(err.description()))?; data = wabt::wat2wasm(data).map_err(|err| String::from(err.description()))?;
} }
let mut resolver = NullResolver {}; let mut resolver = NullResolver {};
let mut code = Code::new(); let mut jit_code = JITCode::new();
let mut world = let mut instance_plus =
InstanceWorld::new(&mut code, isa, &data, &mut resolver).map_err(|e| e.to_string())?; InstancePlus::new(&mut jit_code, isa, &data, &mut resolver).map_err(|e| e.to_string())?;
if let Some(ref f) = args.flag_invoke { if let Some(ref f) = args.flag_invoke {
match world match instance_plus
.invoke(&mut code, isa, &f, &[]) .invoke(&mut jit_code, isa, &f, &[])
.map_err(|e| e.to_string())? .map_err(|e| e.to_string())?
{ {
ActionOutcome::Returned { .. } => {} ActionOutcome::Returned { .. } => {}
@@ -166,42 +159,6 @@ fn handle_module(args: &Args, path: &Path, isa: &TargetIsa) -> Result<(), String
} }
} }
if args.flag_memory {
let mut input = String::new();
println!("Inspecting memory");
println!("Type 'quit' to exit.");
loop {
input.clear();
print!("Memory index, offset, length (e.g. 0,0,4): ");
let _ = stdout().flush();
match io::stdin().read_line(&mut input) {
Ok(_) => {
input.pop();
if input == "quit" {
break;
}
let split: Vec<&str> = input.split(',').collect();
if split.len() != 3 {
break;
}
let memory = world
.inspect_memory(
MemoryIndex::new(str::parse(split[0]).unwrap()),
str::parse(split[1]).unwrap(),
str::parse(split[2]).unwrap(),
)
.map_err(|e| e.to_string())?;
let mut s = memory.iter().fold(String::from("#"), |mut acc, byte| {
acc.push_str(format!("{:02x}_", byte).as_str());
acc
});
s.pop();
println!("{}", s);
}
Err(error) => return Err(String::from(error.description())),
}
}
}
Ok(()) Ok(())
} }
@@ -211,7 +168,7 @@ mod tests {
use cranelift_codegen::settings::Configurable; use cranelift_codegen::settings::Configurable;
use std::path::PathBuf; use std::path::PathBuf;
use wabt; use wabt;
use wasmtime_execute::{Code, InstanceWorld, NullResolver}; use wasmtime_execute::{InstancePlus, JITCode, NullResolver};
const PATH_MODULE_RS2WASM_ADD_FUNC: &str = r"filetests/rs2wasm-add-func.wat"; const PATH_MODULE_RS2WASM_ADD_FUNC: &str = r"filetests/rs2wasm-add-func.wat";
@@ -234,8 +191,8 @@ mod tests {
let isa = isa_builder.finish(settings::Flags::new(flag_builder)); let isa = isa_builder.finish(settings::Flags::new(flag_builder));
let mut resolver = NullResolver {}; let mut resolver = NullResolver {};
let mut code = Code::new(); let mut code = JITCode::new();
let world = InstanceWorld::new(&mut code, &*isa, &data, &mut resolver); let instance = InstancePlus::new(&mut code, &*isa, &data, &mut resolver);
assert!(world.is_ok()); assert!(instance.is_ok());
} }
} }

View File

@@ -94,7 +94,12 @@ fn main() {
} }
let isa = isa_builder.finish(settings::Flags::new(flag_builder)); let isa = isa_builder.finish(settings::Flags::new(flag_builder));
let mut wast_context = WastContext::new().expect("Error creating WastContext"); let mut wast_context = WastContext::new();
wast_context
.register_spectest()
.expect("error instantiating \"spectest\"");
for filename in &args.arg_file { for filename in &args.arg_file {
wast_context wast_context
.run_file(&*isa, Path::new(&filename)) .run_file(&*isa, Path::new(&filename))

View File

@@ -8,7 +8,7 @@ use cranelift_codegen::settings::Configurable;
use std::path::Path; use std::path::Path;
use wasmtime_wast::WastContext; use wasmtime_wast::WastContext;
include!(concat!(env!("OUT_DIR"), "/run_wast_files.rs")); include!(concat!(env!("OUT_DIR"), "/wast_testsuite_tests.rs"));
#[cfg(test)] #[cfg(test)]
fn native_isa() -> Box<isa::TargetIsa> { fn native_isa() -> Box<isa::TargetIsa> {
@@ -18,5 +18,6 @@ fn native_isa() -> Box<isa::TargetIsa> {
let isa_builder = cranelift_native::builder().unwrap_or_else(|_| { let isa_builder = cranelift_native::builder().unwrap_or_else(|_| {
panic!("host machine is not a supported target"); panic!("host machine is not a supported target");
}); });
isa_builder.finish(settings::Flags::new(flag_builder)) isa_builder.finish(settings::Flags::new(flag_builder))
} }