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:
2
.gitmodules
vendored
2
.gitmodules
vendored
@@ -1,3 +1,3 @@
|
||||
[submodule "lib/wast/spec_testsuite"]
|
||||
path = lib/wast/spec_testsuite
|
||||
path = spec_testsuite
|
||||
url = https://github.com/WebAssembly/testsuite
|
||||
|
||||
@@ -14,8 +14,8 @@ name = "wasmtime"
|
||||
path = "src/wasmtime.rs"
|
||||
|
||||
[[bin]]
|
||||
name = "run_wast"
|
||||
path = "src/run_wast.rs"
|
||||
name = "wast"
|
||||
path = "src/wast.rs"
|
||||
|
||||
[[bin]]
|
||||
name = "wasm2obj"
|
||||
|
||||
116
build.rs
Normal file
116
build.rs
Normal 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
|
||||
}
|
||||
@@ -132,14 +132,6 @@ impl<'module_environment> FuncEnvironment<'module_environment> {
|
||||
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 {
|
||||
self.vmctx.unwrap_or_else(|| {
|
||||
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);
|
||||
|
||||
// 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.
|
||||
match self.module.table_plans[table_index].style {
|
||||
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))
|
||||
}
|
||||
|
||||
@@ -608,10 +611,12 @@ impl<'module_environment> cranelift_wasm::FuncEnvironment for FuncEnvironment<'m
|
||||
callee: ir::FuncRef,
|
||||
call_args: &[ir::Value],
|
||||
) -> 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.
|
||||
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));
|
||||
}
|
||||
|
||||
@@ -623,9 +628,18 @@ impl<'module_environment> cranelift_wasm::FuncEnvironment for FuncEnvironment<'m
|
||||
let base = pos
|
||||
.ins()
|
||||
.global_value(pointer_type, imported_functions_base);
|
||||
let offset = self.offsets.index_vmfunction_body_import(callee_index);
|
||||
|
||||
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))
|
||||
}
|
||||
|
||||
|
||||
@@ -48,10 +48,10 @@ mod vmoffsets;
|
||||
pub use compilation::{
|
||||
compile_module, Compilation, CompileError, RelocSink, Relocation, RelocationTarget, Relocations,
|
||||
};
|
||||
pub use module::{
|
||||
DataInitializer, Export, MemoryPlan, MemoryStyle, Module, TableElements, TablePlan, TableStyle,
|
||||
pub use module::{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 vmoffsets::VMOffsets;
|
||||
|
||||
|
||||
@@ -75,7 +75,7 @@ impl MemoryStyle {
|
||||
|
||||
/// A WebAssembly linear memory description along with our chosen style for
|
||||
/// implementing it.
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct MemoryPlan {
|
||||
/// The WebAssembly linear memory description.
|
||||
pub memory: Memory,
|
||||
@@ -113,7 +113,7 @@ impl TableStyle {
|
||||
|
||||
/// A WebAssembly table description along with our chosen style for
|
||||
/// implementing it.
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct TablePlan {
|
||||
/// The WebAssembly table description.
|
||||
pub table: cranelift_wasm::Table,
|
||||
@@ -277,34 +277,3 @@ impl Module {
|
||||
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(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
use cranelift_codegen::ir;
|
||||
use cranelift_codegen::ir::{AbiParam, ArgumentPurpose};
|
||||
use cranelift_codegen::isa;
|
||||
use cranelift_entity::PrimaryMap;
|
||||
use cranelift_wasm::{
|
||||
self, translate_module, FuncIndex, Global, GlobalIndex, Memory, MemoryIndex, SignatureIndex,
|
||||
Table, TableIndex, WasmResult,
|
||||
self, translate_module, DefinedFuncIndex, FuncIndex, Global, GlobalIndex, Memory, MemoryIndex,
|
||||
SignatureIndex, Table, TableIndex, WasmResult,
|
||||
};
|
||||
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::string::String;
|
||||
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));
|
||||
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(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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`.
|
||||
impl VMOffsets {
|
||||
/// The size of the `current_elements` field.
|
||||
@@ -174,9 +194,14 @@ impl VMOffsets {
|
||||
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`.
|
||||
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
|
||||
}
|
||||
|
||||
/// 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`.
|
||||
fn index_vmtable_import(&self, index: TableIndex) -> i32 {
|
||||
cast::i32(
|
||||
@@ -286,14 +322,18 @@ impl VMOffsets {
|
||||
}
|
||||
|
||||
/// Return the offset from the `imported_functions` pointer to the
|
||||
/// `*const VMFunctionBody` index `index`.
|
||||
pub fn index_vmfunction_body_import(&self, index: FuncIndex) -> i32 {
|
||||
cast::i32(
|
||||
index
|
||||
.as_u32()
|
||||
.checked_mul(u32::from(self.size_of_vmfunction_body_ptr()))
|
||||
.unwrap(),
|
||||
)
|
||||
/// `body` field in `*const VMFunctionBody` index `index`.
|
||||
pub fn index_vmfunction_import_body(&self, index: FuncIndex) -> i32 {
|
||||
self.index_vmfunction_import(index)
|
||||
.checked_add(i32::from(self.vmfunction_import_body()))
|
||||
.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()
|
||||
}
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ use std::fmt;
|
||||
use std::string::String;
|
||||
use std::vec::Vec;
|
||||
use wasmtime_environ::CompileError;
|
||||
use wasmtime_runtime::InstantiationError;
|
||||
|
||||
/// A runtime value.
|
||||
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
|
||||
@@ -110,10 +111,6 @@ pub enum ActionError {
|
||||
#[fail(display = "Unknown field: {}", _0)]
|
||||
Field(String),
|
||||
|
||||
/// An index was out of bounds.
|
||||
#[fail(display = "Index out of bounds: {}", _0)]
|
||||
Index(u64),
|
||||
|
||||
/// The field was present but was the wrong kind (eg. function, table, global, or memory).
|
||||
#[fail(display = "Kind error: {}", _0)]
|
||||
Kind(String),
|
||||
@@ -126,9 +123,10 @@ pub enum ActionError {
|
||||
#[fail(display = "WebAssembly compilation error: {}", _0)]
|
||||
Compile(CompileError),
|
||||
|
||||
/// Some runtime resource was unavailable or insufficient.
|
||||
#[fail(display = "Runtime resource error: {}", _0)]
|
||||
Resource(String),
|
||||
/// Some runtime resource was unavailable or insufficient, or the start function
|
||||
/// trapped.
|
||||
#[fail(display = "Instantiation error: {}", _0)]
|
||||
Instantiate(InstantiationError),
|
||||
|
||||
/// Link error.
|
||||
#[fail(display = "Link error: {}", _0)]
|
||||
|
||||
287
lib/execute/src/instance_plus.rs
Normal file
287
lib/execute/src/instance_plus.rs
Normal file
@@ -0,0 +1,287 @@
|
||||
use action::{ActionError, ActionOutcome, RuntimeValue};
|
||||
use cranelift_codegen::{ir, isa};
|
||||
use cranelift_entity::{BoxedSlice, PrimaryMap};
|
||||
use cranelift_wasm::DefinedFuncIndex;
|
||||
use jit_code::JITCode;
|
||||
use link::link_module;
|
||||
use resolver::Resolver;
|
||||
use std::cmp::max;
|
||||
use std::rc::Rc;
|
||||
use std::slice;
|
||||
use std::string::String;
|
||||
use std::vec::Vec;
|
||||
use std::{mem, ptr};
|
||||
use trampoline_park::TrampolinePark;
|
||||
use wasmtime_environ::{
|
||||
compile_module, Compilation, CompileError, DataInitializer, Module, ModuleEnvironment, Tunables,
|
||||
};
|
||||
use wasmtime_runtime::{
|
||||
wasmtime_call_trampoline, Export, Imports, Instance, InstantiationError, VMFunctionBody,
|
||||
};
|
||||
|
||||
/// `InstancePlus` holds an `Instance` and adds support for performing actions
|
||||
/// such as the "invoke" command in wast.
|
||||
///
|
||||
/// TODO: Think of a better name.
|
||||
#[derive(Debug)]
|
||||
pub struct InstancePlus {
|
||||
/// The contained instance.
|
||||
pub instance: Box<Instance>,
|
||||
|
||||
/// Trampolines for calling into JIT code.
|
||||
trampolines: TrampolinePark,
|
||||
}
|
||||
|
||||
impl InstancePlus {
|
||||
/// Create a new `InstancePlus` by compiling the wasm module in `data` and instatiating it.
|
||||
pub fn new(
|
||||
jit_code: &mut JITCode,
|
||||
isa: &isa::TargetIsa,
|
||||
data: &[u8],
|
||||
resolver: &mut Resolver,
|
||||
) -> Result<Self, ActionError> {
|
||||
let mut module = Module::new();
|
||||
|
||||
// TODO: Allow the tunables to be overridden.
|
||||
let tunables = Tunables::default();
|
||||
|
||||
let (lazy_function_body_inputs, lazy_data_initializers) = {
|
||||
let environ = ModuleEnvironment::new(isa, &mut module, tunables);
|
||||
|
||||
let translation = environ
|
||||
.translate(&data)
|
||||
.map_err(|error| ActionError::Compile(CompileError::Wasm(error)))?;
|
||||
|
||||
(
|
||||
translation.lazy.function_body_inputs,
|
||||
translation.lazy.data_initializers,
|
||||
)
|
||||
};
|
||||
|
||||
let (compilation, relocations) = compile_module(&module, &lazy_function_body_inputs, isa)
|
||||
.map_err(ActionError::Compile)?;
|
||||
|
||||
let allocated_functions = allocate_functions(jit_code, compilation).map_err(|message| {
|
||||
ActionError::Instantiate(InstantiationError::Resource(format!(
|
||||
"failed to allocate memory for functions: {}",
|
||||
message
|
||||
)))
|
||||
})?;
|
||||
|
||||
let imports = link_module(&module, &allocated_functions, relocations, resolver)
|
||||
.map_err(ActionError::Link)?;
|
||||
|
||||
// Gather up the pointers to the compiled functions.
|
||||
let finished_functions: BoxedSlice<DefinedFuncIndex, *const VMFunctionBody> =
|
||||
allocated_functions
|
||||
.into_iter()
|
||||
.map(|(_index, allocated)| {
|
||||
let fatptr: *const [VMFunctionBody] = *allocated;
|
||||
fatptr as *const VMFunctionBody
|
||||
})
|
||||
.collect::<PrimaryMap<_, _>>()
|
||||
.into_boxed_slice();
|
||||
|
||||
// Make all code compiled thus far executable.
|
||||
jit_code.publish();
|
||||
|
||||
Self::with_parts(
|
||||
Rc::new(module),
|
||||
finished_functions,
|
||||
imports,
|
||||
lazy_data_initializers,
|
||||
)
|
||||
}
|
||||
|
||||
/// Construct a new `InstancePlus` from the parts needed to produce an `Instance`.
|
||||
pub fn with_parts(
|
||||
module: Rc<Module>,
|
||||
finished_functions: BoxedSlice<DefinedFuncIndex, *const VMFunctionBody>,
|
||||
imports: Imports,
|
||||
data_initializers: Vec<DataInitializer>,
|
||||
) -> Result<Self, ActionError> {
|
||||
let instance = Instance::new(module, finished_functions, imports, data_initializers)
|
||||
.map_err(ActionError::Instantiate)?;
|
||||
|
||||
Ok(Self::with_instance(instance))
|
||||
}
|
||||
|
||||
/// Construct a new `InstancePlus` from an existing instance.
|
||||
pub fn with_instance(instance: Box<Instance>) -> Self {
|
||||
Self {
|
||||
instance,
|
||||
trampolines: TrampolinePark::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Invoke a function in this `Instance` identified by an export name.
|
||||
pub fn invoke(
|
||||
&mut self,
|
||||
jit_code: &mut JITCode,
|
||||
isa: &isa::TargetIsa,
|
||||
function_name: &str,
|
||||
args: &[RuntimeValue],
|
||||
) -> Result<ActionOutcome, ActionError> {
|
||||
let (address, signature, callee_vmctx) = match self.instance.lookup(function_name) {
|
||||
Some(Export::Function {
|
||||
address,
|
||||
signature,
|
||||
vmctx,
|
||||
}) => (address, signature, vmctx),
|
||||
Some(_) => {
|
||||
return Err(ActionError::Kind(format!(
|
||||
"exported item \"{}\" is not a function",
|
||||
function_name
|
||||
)))
|
||||
}
|
||||
None => {
|
||||
return Err(ActionError::Field(format!(
|
||||
"no export named \"{}\"",
|
||||
function_name
|
||||
)))
|
||||
}
|
||||
};
|
||||
|
||||
for (index, value) in args.iter().enumerate() {
|
||||
assert_eq!(value.value_type(), signature.params[index].value_type);
|
||||
}
|
||||
|
||||
// TODO: Support values larger than u64.
|
||||
let mut values_vec: Vec<u64> = Vec::new();
|
||||
let value_size = mem::size_of::<u64>();
|
||||
values_vec.resize(max(signature.params.len(), signature.returns.len()), 0u64);
|
||||
|
||||
// Store the argument values into `values_vec`.
|
||||
for (index, arg) in args.iter().enumerate() {
|
||||
unsafe {
|
||||
let ptr = values_vec.as_mut_ptr().add(index);
|
||||
|
||||
match arg {
|
||||
RuntimeValue::I32(x) => ptr::write(ptr as *mut i32, *x),
|
||||
RuntimeValue::I64(x) => ptr::write(ptr as *mut i64, *x),
|
||||
RuntimeValue::F32(x) => ptr::write(ptr as *mut u32, *x),
|
||||
RuntimeValue::F64(x) => ptr::write(ptr as *mut u64, *x),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Get the trampoline to call for this function.
|
||||
let exec_code_buf = self
|
||||
.trampolines
|
||||
.get(jit_code, isa, address, &signature, value_size)?;
|
||||
|
||||
// Make all JIT code produced thus far executable.
|
||||
jit_code.publish();
|
||||
|
||||
// Call the trampoline.
|
||||
if let Err(message) = unsafe {
|
||||
wasmtime_call_trampoline(
|
||||
exec_code_buf,
|
||||
values_vec.as_mut_ptr() as *mut u8,
|
||||
callee_vmctx,
|
||||
)
|
||||
} {
|
||||
return Ok(ActionOutcome::Trapped { message });
|
||||
}
|
||||
|
||||
// Load the return values out of `values_vec`.
|
||||
let values = signature
|
||||
.returns
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(index, abi_param)| unsafe {
|
||||
let ptr = values_vec.as_ptr().add(index);
|
||||
|
||||
match abi_param.value_type {
|
||||
ir::types::I32 => RuntimeValue::I32(ptr::read(ptr as *const i32)),
|
||||
ir::types::I64 => RuntimeValue::I64(ptr::read(ptr as *const i64)),
|
||||
ir::types::F32 => RuntimeValue::F32(ptr::read(ptr as *const u32)),
|
||||
ir::types::F64 => RuntimeValue::F64(ptr::read(ptr as *const u64)),
|
||||
other => panic!("unsupported value type {:?}", other),
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
|
||||
Ok(ActionOutcome::Returned { values })
|
||||
}
|
||||
|
||||
/// Returns a slice of the contents of allocated linear memory.
|
||||
pub fn inspect_memory(
|
||||
&self,
|
||||
memory_name: &str,
|
||||
start: usize,
|
||||
len: usize,
|
||||
) -> Result<&[u8], ActionError> {
|
||||
let address = match unsafe { self.instance.lookup_immutable(memory_name) } {
|
||||
Some(Export::Memory {
|
||||
address,
|
||||
memory: _memory,
|
||||
vmctx: _vmctx,
|
||||
}) => address,
|
||||
Some(_) => {
|
||||
return Err(ActionError::Kind(format!(
|
||||
"exported item \"{}\" is not a linear memory",
|
||||
memory_name
|
||||
)))
|
||||
}
|
||||
None => {
|
||||
return Err(ActionError::Field(format!(
|
||||
"no export named \"{}\"",
|
||||
memory_name
|
||||
)))
|
||||
}
|
||||
};
|
||||
|
||||
Ok(unsafe {
|
||||
let memory_def = &*address;
|
||||
&slice::from_raw_parts(memory_def.base, memory_def.current_length)[start..start + len]
|
||||
})
|
||||
}
|
||||
|
||||
/// Read a global in this `Instance` identified by an export name.
|
||||
pub fn get(&self, global_name: &str) -> Result<RuntimeValue, ActionError> {
|
||||
let (address, global) = match unsafe { self.instance.lookup_immutable(global_name) } {
|
||||
Some(Export::Global { address, global }) => (address, global),
|
||||
Some(_) => {
|
||||
return Err(ActionError::Kind(format!(
|
||||
"exported item \"{}\" is not a global variable",
|
||||
global_name
|
||||
)))
|
||||
}
|
||||
None => {
|
||||
return Err(ActionError::Field(format!(
|
||||
"no export named \"{}\"",
|
||||
global_name
|
||||
)))
|
||||
}
|
||||
};
|
||||
|
||||
unsafe {
|
||||
let global_def = &*address;
|
||||
Ok(match global.ty {
|
||||
ir::types::I32 => RuntimeValue::I32(*global_def.as_i32()),
|
||||
ir::types::I64 => RuntimeValue::I64(*global_def.as_i64()),
|
||||
ir::types::F32 => RuntimeValue::F32(*global_def.as_f32_bits()),
|
||||
ir::types::F64 => RuntimeValue::F64(*global_def.as_f64_bits()),
|
||||
other => {
|
||||
return Err(ActionError::Type(format!(
|
||||
"global with type {} not supported",
|
||||
other
|
||||
)))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn allocate_functions(
|
||||
jit_code: &mut JITCode,
|
||||
compilation: Compilation,
|
||||
) -> Result<PrimaryMap<DefinedFuncIndex, *mut [VMFunctionBody]>, String> {
|
||||
let mut result = PrimaryMap::with_capacity(compilation.functions.len());
|
||||
for (_, body) in compilation.functions.into_iter() {
|
||||
let fatptr: *mut [VMFunctionBody] = jit_code.allocate_copy_of_byte_slice(body)?;
|
||||
result.push(fatptr);
|
||||
}
|
||||
Ok(result)
|
||||
}
|
||||
@@ -7,15 +7,15 @@ use std::{cmp, mem};
|
||||
use wasmtime_runtime::{Mmap, VMFunctionBody};
|
||||
|
||||
/// Memory manager for executable code.
|
||||
pub struct Code {
|
||||
pub struct JITCode {
|
||||
current: Mmap,
|
||||
mmaps: Vec<Mmap>,
|
||||
position: usize,
|
||||
published: usize,
|
||||
}
|
||||
|
||||
impl Code {
|
||||
/// Create a new `Code` instance.
|
||||
impl JITCode {
|
||||
/// Create a new `JITCode` instance.
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
current: Mmap::new(),
|
||||
@@ -39,16 +39,17 @@ extern crate failure;
|
||||
extern crate failure_derive;
|
||||
|
||||
mod action;
|
||||
mod code;
|
||||
mod export;
|
||||
mod instance_plus;
|
||||
mod jit_code;
|
||||
mod link;
|
||||
mod world;
|
||||
mod resolver;
|
||||
mod trampoline_park;
|
||||
|
||||
pub use action::{ActionError, ActionOutcome, RuntimeValue};
|
||||
pub use code::Code;
|
||||
pub use export::{Export, NullResolver, Resolver};
|
||||
pub use instance_plus::InstancePlus;
|
||||
pub use jit_code::JITCode;
|
||||
pub use link::link_module;
|
||||
pub use world::InstanceWorld;
|
||||
pub use resolver::{NullResolver, Resolver};
|
||||
|
||||
#[cfg(not(feature = "std"))]
|
||||
mod std {
|
||||
|
||||
@@ -1,16 +1,18 @@
|
||||
use cranelift_codegen::binemit::Reloc;
|
||||
use cranelift_entity::PrimaryMap;
|
||||
use cranelift_wasm::{DefinedFuncIndex, Global, GlobalInit, Memory, Table, TableElementType};
|
||||
use export::{Export, FunctionExport, Resolver};
|
||||
use resolver::Resolver;
|
||||
use std::ptr::write_unaligned;
|
||||
use std::string::String;
|
||||
use std::vec::Vec;
|
||||
use wasmtime_environ::{
|
||||
MemoryPlan, MemoryStyle, Module, Relocation, RelocationTarget, Relocations, TablePlan,
|
||||
TableStyle,
|
||||
};
|
||||
use wasmtime_runtime::libcalls;
|
||||
use wasmtime_runtime::{Imports, VMFunctionBody, VMGlobalImport, VMMemoryImport, VMTableImport};
|
||||
use wasmtime_runtime::{
|
||||
Export, Imports, VMFunctionBody, VMFunctionImport, VMGlobalImport, VMMemoryImport,
|
||||
VMTableImport,
|
||||
};
|
||||
|
||||
/// A link error, such as incompatible or unmatched imports/exports.
|
||||
#[derive(Fail, Debug)]
|
||||
@@ -28,7 +30,11 @@ pub fn link_module(
|
||||
for (index, (ref module_name, ref field)) in module.imported_funcs.iter() {
|
||||
match resolver.resolve(module_name, field) {
|
||||
Some(export_value) => match export_value {
|
||||
Export::Function(FunctionExport { address, signature }) => {
|
||||
Export::Function {
|
||||
address,
|
||||
signature,
|
||||
vmctx,
|
||||
} => {
|
||||
let import_signature = &module.signatures[module.functions[index]];
|
||||
if signature != *import_signature {
|
||||
// TODO: If the difference is in the calling convention,
|
||||
@@ -39,7 +45,10 @@ pub fn link_module(
|
||||
signature, import_signature)
|
||||
));
|
||||
}
|
||||
function_imports.push(address);
|
||||
function_imports.push(VMFunctionImport {
|
||||
body: address,
|
||||
vmctx,
|
||||
});
|
||||
}
|
||||
Export::Table { .. } | Export::Memory { .. } | Export::Global { .. } => {
|
||||
return Err(LinkError(format!(
|
||||
@@ -104,12 +113,28 @@ pub fn link_module(
|
||||
memory,
|
||||
} => {
|
||||
let import_memory = &module.memory_plans[index];
|
||||
if is_memory_compatible(&memory, import_memory) {
|
||||
if !is_memory_compatible(&memory, import_memory) {
|
||||
return Err(LinkError(format!(
|
||||
"{}/{}: exported memory incompatible with memory import",
|
||||
module_name, field
|
||||
)));
|
||||
}
|
||||
|
||||
// Sanity-check: Ensure that the imported memory has at least
|
||||
// guard-page protections the importing module expects it to have.
|
||||
match (memory.style, &import_memory.style) {
|
||||
(
|
||||
MemoryStyle::Static { bound },
|
||||
MemoryStyle::Static {
|
||||
bound: import_bound,
|
||||
},
|
||||
) => {
|
||||
assert!(bound >= *import_bound);
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
assert!(memory.offset_guard_size >= import_memory.offset_guard_size);
|
||||
|
||||
memory_imports.push(VMMemoryImport {
|
||||
from: address,
|
||||
vmctx,
|
||||
@@ -161,17 +186,15 @@ pub fn link_module(
|
||||
}
|
||||
}
|
||||
|
||||
let imports = Imports::new(
|
||||
// Apply relocations, now that we have virtual addresses for everything.
|
||||
relocate(allocated_functions, relocations, &module);
|
||||
|
||||
Ok(Imports::new(
|
||||
function_imports,
|
||||
table_imports,
|
||||
memory_imports,
|
||||
global_imports,
|
||||
);
|
||||
|
||||
// Apply relocations, now that we have virtual addresses for everything.
|
||||
relocate(&imports, allocated_functions, relocations, &module);
|
||||
|
||||
Ok(imports)
|
||||
))
|
||||
}
|
||||
|
||||
fn is_global_compatible(exported: &Global, imported: &Global) -> bool {
|
||||
@@ -193,14 +216,6 @@ fn is_global_compatible(exported: &Global, imported: &Global) -> bool {
|
||||
exported_ty == imported_ty && imported_mutability == exported_mutability
|
||||
}
|
||||
|
||||
fn is_table_style_compatible(exported_style: &TableStyle, imported_style: &TableStyle) -> bool {
|
||||
match exported_style {
|
||||
TableStyle::CallerChecksSignature => match imported_style {
|
||||
TableStyle::CallerChecksSignature => true,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn is_table_element_type_compatible(
|
||||
exported_type: TableElementType,
|
||||
imported_type: TableElementType,
|
||||
@@ -225,7 +240,7 @@ fn is_table_compatible(exported: &TablePlan, imported: &TablePlan) -> bool {
|
||||
minimum: exported_minimum,
|
||||
maximum: exported_maximum,
|
||||
},
|
||||
style: exported_style,
|
||||
style: _exported_style,
|
||||
} = exported;
|
||||
let TablePlan {
|
||||
table:
|
||||
@@ -234,30 +249,14 @@ fn is_table_compatible(exported: &TablePlan, imported: &TablePlan) -> bool {
|
||||
minimum: imported_minimum,
|
||||
maximum: imported_maximum,
|
||||
},
|
||||
style: imported_style,
|
||||
style: _imported_style,
|
||||
} = imported;
|
||||
|
||||
is_table_element_type_compatible(*exported_ty, *imported_ty)
|
||||
&& imported_minimum >= exported_minimum
|
||||
&& imported_maximum <= exported_maximum
|
||||
&& is_table_style_compatible(imported_style, exported_style)
|
||||
}
|
||||
|
||||
fn is_memory_style_compatible(exported_style: &MemoryStyle, imported_style: &MemoryStyle) -> bool {
|
||||
match exported_style {
|
||||
MemoryStyle::Dynamic => match imported_style {
|
||||
MemoryStyle::Dynamic => true,
|
||||
_ => false,
|
||||
},
|
||||
MemoryStyle::Static {
|
||||
bound: imported_bound,
|
||||
} => match imported_style {
|
||||
MemoryStyle::Static {
|
||||
bound: exported_bound,
|
||||
} => exported_bound >= imported_bound,
|
||||
_ => false,
|
||||
},
|
||||
}
|
||||
&& imported_minimum <= exported_minimum
|
||||
&& (imported_maximum.is_none()
|
||||
|| (!exported_maximum.is_none()
|
||||
&& imported_maximum.unwrap() >= exported_maximum.unwrap()))
|
||||
}
|
||||
|
||||
fn is_memory_compatible(exported: &MemoryPlan, imported: &MemoryPlan) -> bool {
|
||||
@@ -268,8 +267,8 @@ fn is_memory_compatible(exported: &MemoryPlan, imported: &MemoryPlan) -> bool {
|
||||
maximum: exported_maximum,
|
||||
shared: exported_shared,
|
||||
},
|
||||
style: exported_style,
|
||||
offset_guard_size: exported_offset_guard_size,
|
||||
style: _exported_style,
|
||||
offset_guard_size: _exported_offset_guard_size,
|
||||
} = exported;
|
||||
let MemoryPlan {
|
||||
memory:
|
||||
@@ -278,20 +277,19 @@ fn is_memory_compatible(exported: &MemoryPlan, imported: &MemoryPlan) -> bool {
|
||||
maximum: imported_maximum,
|
||||
shared: imported_shared,
|
||||
},
|
||||
style: imported_style,
|
||||
offset_guard_size: imported_offset_guard_size,
|
||||
style: _imported_style,
|
||||
offset_guard_size: _imported_offset_guard_size,
|
||||
} = imported;
|
||||
|
||||
imported_minimum >= exported_minimum
|
||||
&& imported_maximum <= exported_maximum
|
||||
imported_minimum <= exported_minimum
|
||||
&& (imported_maximum.is_none()
|
||||
|| (!exported_maximum.is_none()
|
||||
&& imported_maximum.unwrap() >= exported_maximum.unwrap()))
|
||||
&& exported_shared == imported_shared
|
||||
&& is_memory_style_compatible(exported_style, imported_style)
|
||||
&& exported_offset_guard_size >= imported_offset_guard_size
|
||||
}
|
||||
|
||||
/// Performs the relocations inside the function bytecode, provided the necessary metadata.
|
||||
fn relocate(
|
||||
imports: &Imports,
|
||||
allocated_functions: &PrimaryMap<DefinedFuncIndex, *mut [VMFunctionBody]>,
|
||||
relocations: PrimaryMap<DefinedFuncIndex, Vec<Relocation>>,
|
||||
module: &Module,
|
||||
@@ -305,7 +303,7 @@ fn relocate(
|
||||
let fatptr: *const [VMFunctionBody] = allocated_functions[f];
|
||||
fatptr as *const VMFunctionBody as usize
|
||||
}
|
||||
None => imports.functions[index] as usize,
|
||||
None => panic!("direct call to import"),
|
||||
},
|
||||
RelocationTarget::Memory32Grow => wasmtime_memory32_grow as usize,
|
||||
RelocationTarget::Memory32Size => wasmtime_memory32_size as usize,
|
||||
|
||||
16
lib/execute/src/resolver.rs
Normal file
16
lib/execute/src/resolver.rs
Normal file
@@ -0,0 +1,16 @@
|
||||
use wasmtime_runtime::Export;
|
||||
|
||||
/// Import resolver connects imports with available exported values.
|
||||
pub trait Resolver {
|
||||
/// Resolve the given module/field combo.
|
||||
fn resolve(&mut self, module: &str, field: &str) -> Option<Export>;
|
||||
}
|
||||
|
||||
/// `Resolver` implementation that always resolves to `None`.
|
||||
pub struct NullResolver {}
|
||||
|
||||
impl Resolver for NullResolver {
|
||||
fn resolve(&mut self, _module: &str, _field: &str) -> Option<Export> {
|
||||
None
|
||||
}
|
||||
}
|
||||
152
lib/execute/src/trampoline_park.rs
Normal file
152
lib/execute/src/trampoline_park.rs
Normal file
@@ -0,0 +1,152 @@
|
||||
use action::ActionError;
|
||||
use cranelift_codegen::ir::InstBuilder;
|
||||
use cranelift_codegen::Context;
|
||||
use cranelift_codegen::{binemit, ir, isa};
|
||||
use cranelift_frontend::{FunctionBuilder, FunctionBuilderContext};
|
||||
use jit_code::JITCode;
|
||||
use std::collections::HashMap;
|
||||
use std::fmt;
|
||||
use wasmtime_environ::{CompileError, RelocSink};
|
||||
use wasmtime_runtime::{InstantiationError, VMFunctionBody};
|
||||
|
||||
pub struct TrampolinePark {
|
||||
/// Memoized per-function trampolines.
|
||||
memoized: HashMap<*const VMFunctionBody, *const VMFunctionBody>,
|
||||
|
||||
/// The `FunctionBuilderContext`, shared between function compilations.
|
||||
fn_builder_ctx: FunctionBuilderContext,
|
||||
}
|
||||
|
||||
impl TrampolinePark {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
memoized: HashMap::new(),
|
||||
fn_builder_ctx: FunctionBuilderContext::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get(
|
||||
&mut self,
|
||||
jit_code: &mut JITCode,
|
||||
isa: &isa::TargetIsa,
|
||||
callee_address: *const VMFunctionBody,
|
||||
signature: &ir::Signature,
|
||||
value_size: usize,
|
||||
) -> Result<*const VMFunctionBody, ActionError> {
|
||||
use std::collections::hash_map::Entry::{Occupied, Vacant};
|
||||
Ok(match self.memoized.entry(callee_address) {
|
||||
Occupied(entry) => *entry.get(),
|
||||
Vacant(entry) => {
|
||||
let body = make_trampoline(
|
||||
&mut self.fn_builder_ctx,
|
||||
jit_code,
|
||||
isa,
|
||||
callee_address,
|
||||
signature,
|
||||
value_size,
|
||||
)?;
|
||||
entry.insert(body);
|
||||
body
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for TrampolinePark {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
// The `fn_builder_ctx` field is just a cache and has no logical state.
|
||||
write!(f, "{:?}", self.memoized)
|
||||
}
|
||||
}
|
||||
|
||||
fn make_trampoline(
|
||||
fn_builder_ctx: &mut FunctionBuilderContext,
|
||||
jit_code: &mut JITCode,
|
||||
isa: &isa::TargetIsa,
|
||||
callee_address: *const VMFunctionBody,
|
||||
signature: &ir::Signature,
|
||||
value_size: usize,
|
||||
) -> Result<*const VMFunctionBody, ActionError> {
|
||||
let pointer_type = isa.pointer_type();
|
||||
let mut wrapper_sig = ir::Signature::new(isa.frontend_config().default_call_conv);
|
||||
|
||||
// Add the `values_vec` parameter.
|
||||
wrapper_sig.params.push(ir::AbiParam::new(pointer_type));
|
||||
// Add the `vmctx` parameter.
|
||||
wrapper_sig.params.push(ir::AbiParam::special(
|
||||
pointer_type,
|
||||
ir::ArgumentPurpose::VMContext,
|
||||
));
|
||||
|
||||
let mut context = Context::new();
|
||||
context.func = ir::Function::with_name_signature(ir::ExternalName::user(0, 0), wrapper_sig);
|
||||
|
||||
{
|
||||
let mut builder = FunctionBuilder::new(&mut context.func, fn_builder_ctx);
|
||||
let block0 = builder.create_ebb();
|
||||
|
||||
builder.append_ebb_params_for_function_params(block0);
|
||||
builder.switch_to_block(block0);
|
||||
builder.seal_block(block0);
|
||||
|
||||
let mut callee_args = Vec::new();
|
||||
let pointer_type = isa.pointer_type();
|
||||
|
||||
let (values_vec_ptr_val, vmctx_ptr_val) = {
|
||||
let params = builder.func.dfg.ebb_params(block0);
|
||||
(params[0], params[1])
|
||||
};
|
||||
|
||||
// Load the argument values out of `values_vec`.
|
||||
let mflags = ir::MemFlags::trusted();
|
||||
for (i, r) in signature.params.iter().enumerate() {
|
||||
let value = match r.purpose {
|
||||
ir::ArgumentPurpose::Normal => builder.ins().load(
|
||||
r.value_type,
|
||||
mflags,
|
||||
values_vec_ptr_val,
|
||||
(i * value_size) as i32,
|
||||
),
|
||||
ir::ArgumentPurpose::VMContext => vmctx_ptr_val,
|
||||
other => panic!("unsupported argument purpose {}", other),
|
||||
};
|
||||
callee_args.push(value);
|
||||
}
|
||||
|
||||
let new_sig = builder.import_signature(signature.clone());
|
||||
|
||||
// TODO: It's possible to make this a direct call. We just need Cranelift
|
||||
// to support functions declared with an immediate integer address.
|
||||
// ExternalName::Absolute(u64). Let's do it.
|
||||
let callee_value = builder.ins().iconst(pointer_type, callee_address as i64);
|
||||
let call = builder
|
||||
.ins()
|
||||
.call_indirect(new_sig, callee_value, &callee_args);
|
||||
|
||||
let results = builder.func.dfg.inst_results(call).to_vec();
|
||||
|
||||
// Store the return values into `values_vec`.
|
||||
let mflags = ir::MemFlags::trusted();
|
||||
for (i, r) in results.iter().enumerate() {
|
||||
builder
|
||||
.ins()
|
||||
.store(mflags, *r, values_vec_ptr_val, (i * value_size) as i32);
|
||||
}
|
||||
|
||||
builder.ins().return_(&[]);
|
||||
builder.finalize()
|
||||
}
|
||||
|
||||
let mut code_buf: Vec<u8> = Vec::new();
|
||||
let mut reloc_sink = RelocSink::new();
|
||||
let mut trap_sink = binemit::NullTrapSink {};
|
||||
context
|
||||
.compile_and_emit(isa, &mut code_buf, &mut reloc_sink, &mut trap_sink)
|
||||
.map_err(|error| ActionError::Compile(CompileError::Codegen(error)))?;
|
||||
assert!(reloc_sink.func_relocs.is_empty());
|
||||
|
||||
Ok(jit_code
|
||||
.allocate_copy_of_byte_slice(&code_buf)
|
||||
.map_err(|message| ActionError::Instantiate(InstantiationError::Resource(message)))?
|
||||
.as_ptr())
|
||||
}
|
||||
@@ -1,553 +0,0 @@
|
||||
use action::{ActionError, ActionOutcome, RuntimeValue};
|
||||
use code::Code;
|
||||
use cranelift_codegen::ir::InstBuilder;
|
||||
use cranelift_codegen::Context;
|
||||
use cranelift_codegen::{binemit, ir, isa};
|
||||
use cranelift_entity::{BoxedSlice, EntityRef, PrimaryMap};
|
||||
use cranelift_frontend::{FunctionBuilder, FunctionBuilderContext};
|
||||
use cranelift_wasm::{
|
||||
DefinedFuncIndex, DefinedGlobalIndex, DefinedMemoryIndex, DefinedTableIndex, FuncIndex,
|
||||
GlobalIndex, MemoryIndex, TableIndex,
|
||||
};
|
||||
use export::Resolver;
|
||||
use link::link_module;
|
||||
use std::cmp::max;
|
||||
use std::collections::HashMap;
|
||||
use std::slice;
|
||||
use std::string::String;
|
||||
use std::vec::Vec;
|
||||
use std::{mem, ptr};
|
||||
use wasmtime_environ::{
|
||||
compile_module, Compilation, CompileError, Export, Module, ModuleEnvironment, RelocSink,
|
||||
Tunables,
|
||||
};
|
||||
use wasmtime_runtime::{
|
||||
wasmtime_call_trampoline, wasmtime_init_eager, wasmtime_init_finish, Instance, VMContext,
|
||||
VMFunctionBody, VMGlobalDefinition, VMGlobalImport, VMMemoryDefinition, VMMemoryImport,
|
||||
VMTableDefinition, VMTableImport,
|
||||
};
|
||||
|
||||
/// A module, an instance of that module, and accompanying compilation artifacts.
|
||||
///
|
||||
/// TODO: Rename and reorganize this.
|
||||
pub struct InstanceWorld {
|
||||
module: Module,
|
||||
instance: Instance,
|
||||
|
||||
/// Pointers to functions in executable memory.
|
||||
finished_functions: BoxedSlice<DefinedFuncIndex, *const VMFunctionBody>,
|
||||
|
||||
/// Trampolines for calling into JIT code.
|
||||
trampolines: TrampolinePark,
|
||||
}
|
||||
|
||||
impl InstanceWorld {
|
||||
/// Create a new `InstanceWorld` by compiling the wasm module in `data` and instatiating it.
|
||||
///
|
||||
/// `finished_functions` holds the function bodies
|
||||
/// which have been placed in executable memory and linked.
|
||||
pub fn new(
|
||||
code: &mut Code,
|
||||
isa: &isa::TargetIsa,
|
||||
data: &[u8],
|
||||
resolver: &mut Resolver,
|
||||
) -> Result<Self, ActionError> {
|
||||
let mut module = Module::new();
|
||||
// TODO: Allow the tunables to be overridden.
|
||||
let tunables = Tunables::default();
|
||||
let (lazy_function_body_inputs, lazy_data_initializers) = {
|
||||
let environ = ModuleEnvironment::new(isa, &mut module, tunables);
|
||||
|
||||
let translation = environ
|
||||
.translate(&data)
|
||||
.map_err(|error| ActionError::Compile(CompileError::Wasm(error)))?;
|
||||
|
||||
(
|
||||
translation.lazy.function_body_inputs,
|
||||
translation.lazy.data_initializers,
|
||||
)
|
||||
};
|
||||
|
||||
let (compilation, relocations) = compile_module(&module, &lazy_function_body_inputs, isa)
|
||||
.map_err(ActionError::Compile)?;
|
||||
|
||||
let allocated_functions =
|
||||
allocate_functions(code, compilation).map_err(ActionError::Resource)?;
|
||||
|
||||
let imports = link_module(&module, &allocated_functions, relocations, resolver)
|
||||
.map_err(ActionError::Link)?;
|
||||
|
||||
let finished_functions: BoxedSlice<DefinedFuncIndex, *const VMFunctionBody> =
|
||||
allocated_functions
|
||||
.into_iter()
|
||||
.map(|(_index, allocated)| {
|
||||
let fatptr: *const [VMFunctionBody] = *allocated;
|
||||
fatptr as *const VMFunctionBody
|
||||
})
|
||||
.collect::<PrimaryMap<_, _>>()
|
||||
.into_boxed_slice();
|
||||
|
||||
let instance = Instance::new(
|
||||
&module,
|
||||
&finished_functions,
|
||||
imports,
|
||||
&lazy_data_initializers,
|
||||
)
|
||||
.map_err(ActionError::Resource)?;
|
||||
|
||||
let fn_builder_ctx = FunctionBuilderContext::new();
|
||||
|
||||
let mut result = Self {
|
||||
module,
|
||||
instance,
|
||||
finished_functions,
|
||||
trampolines: TrampolinePark {
|
||||
memo: HashMap::new(),
|
||||
fn_builder_ctx,
|
||||
},
|
||||
};
|
||||
|
||||
// The WebAssembly spec specifies that the start function is
|
||||
// invoked automatically at instantiation time.
|
||||
match result.invoke_start_function(code, isa)? {
|
||||
ActionOutcome::Returned { .. } => {}
|
||||
ActionOutcome::Trapped { message } => {
|
||||
// Instantiation fails if the start function traps.
|
||||
return Err(ActionError::Start(message));
|
||||
}
|
||||
}
|
||||
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
fn get_imported_function(&self, index: FuncIndex) -> Option<*const VMFunctionBody> {
|
||||
if index.index() < self.module.imported_funcs.len() {
|
||||
Some(unsafe { self.instance.vmctx().imported_function(index) })
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Add an accessor for table elements.
|
||||
#[allow(dead_code)]
|
||||
fn get_imported_table(&self, index: TableIndex) -> Option<&VMTableImport> {
|
||||
if index.index() < self.module.imported_tables.len() {
|
||||
Some(unsafe { self.instance.vmctx().imported_table(index) })
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn get_imported_memory(&self, index: MemoryIndex) -> Option<&VMMemoryImport> {
|
||||
if index.index() < self.module.imported_memories.len() {
|
||||
Some(unsafe { self.instance.vmctx().imported_memory(index) })
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn get_imported_global(&self, index: GlobalIndex) -> Option<&VMGlobalImport> {
|
||||
if index.index() < self.module.imported_globals.len() {
|
||||
Some(unsafe { self.instance.vmctx().imported_global(index) })
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn get_finished_function(&self, index: DefinedFuncIndex) -> Option<*const VMFunctionBody> {
|
||||
self.finished_functions.get(index).cloned()
|
||||
}
|
||||
|
||||
// TODO: Add an accessor for table elements.
|
||||
#[allow(dead_code)]
|
||||
fn get_defined_table(&self, index: DefinedTableIndex) -> Option<&VMTableDefinition> {
|
||||
if self.module.table_index(index).index() < self.module.table_plans.len() {
|
||||
Some(unsafe { self.instance.vmctx().table(index) })
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn get_defined_memory(&self, index: DefinedMemoryIndex) -> Option<&VMMemoryDefinition> {
|
||||
if self.module.memory_index(index).index() < self.module.memory_plans.len() {
|
||||
Some(unsafe { self.instance.vmctx().memory(index) })
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn get_defined_global(&self, index: DefinedGlobalIndex) -> Option<&VMGlobalDefinition> {
|
||||
if self.module.global_index(index).index() < self.module.globals.len() {
|
||||
Some(unsafe { self.instance.vmctx().global(index) })
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// Invoke a function in this `InstanceWorld` by name.
|
||||
pub fn invoke(
|
||||
&mut self,
|
||||
code: &mut Code,
|
||||
isa: &isa::TargetIsa,
|
||||
function_name: &str,
|
||||
args: &[RuntimeValue],
|
||||
) -> Result<ActionOutcome, ActionError> {
|
||||
let fn_index = match self.module.exports.get(function_name) {
|
||||
Some(Export::Function(index)) => *index,
|
||||
Some(_) => {
|
||||
return Err(ActionError::Kind(format!(
|
||||
"exported item \"{}\" is not a function",
|
||||
function_name
|
||||
)))
|
||||
}
|
||||
None => {
|
||||
return Err(ActionError::Field(format!(
|
||||
"no export named \"{}\"",
|
||||
function_name
|
||||
)))
|
||||
}
|
||||
};
|
||||
|
||||
self.invoke_by_index(code, isa, fn_index, args)
|
||||
}
|
||||
|
||||
/// Invoke the WebAssembly start function of the instance, if one is present.
|
||||
fn invoke_start_function(
|
||||
&mut self,
|
||||
code: &mut Code,
|
||||
isa: &isa::TargetIsa,
|
||||
) -> Result<ActionOutcome, ActionError> {
|
||||
if let Some(start_index) = self.module.start_func {
|
||||
self.invoke_by_index(code, isa, start_index, &[])
|
||||
} else {
|
||||
// No start function, just return nothing.
|
||||
Ok(ActionOutcome::Returned { values: vec![] })
|
||||
}
|
||||
}
|
||||
|
||||
/// Calls the given indexed function, passing its return values and returning
|
||||
/// its results.
|
||||
fn invoke_by_index(
|
||||
&mut self,
|
||||
code: &mut Code,
|
||||
isa: &isa::TargetIsa,
|
||||
fn_index: FuncIndex,
|
||||
args: &[RuntimeValue],
|
||||
) -> Result<ActionOutcome, ActionError> {
|
||||
let callee_address = match self.module.defined_func_index(fn_index) {
|
||||
Some(def_fn_index) => self
|
||||
.get_finished_function(def_fn_index)
|
||||
.ok_or_else(|| ActionError::Index(def_fn_index.index() as u64))?,
|
||||
None => self
|
||||
.get_imported_function(fn_index)
|
||||
.ok_or_else(|| ActionError::Index(fn_index.index() as u64))?,
|
||||
};
|
||||
|
||||
// Rather than writing inline assembly to jump to the code region, we use the fact that
|
||||
// the Rust ABI for calling a function with no arguments and no return values matches the one
|
||||
// of the generated code. Thanks to this, we can transmute the code region into a first-class
|
||||
// Rust function and call it.
|
||||
// Ensure that our signal handlers are ready for action.
|
||||
wasmtime_init_eager();
|
||||
wasmtime_init_finish(self.instance.vmctx_mut());
|
||||
|
||||
let signature = &self.module.signatures[self.module.functions[fn_index]];
|
||||
let vmctx: *mut VMContext = self.instance.vmctx_mut();
|
||||
|
||||
for (index, value) in args.iter().enumerate() {
|
||||
assert_eq!(value.value_type(), signature.params[index].value_type);
|
||||
}
|
||||
|
||||
// TODO: Support values larger than u64.
|
||||
let mut values_vec: Vec<u64> = Vec::new();
|
||||
let value_size = mem::size_of::<u64>();
|
||||
values_vec.resize(max(signature.params.len(), signature.returns.len()), 0u64);
|
||||
|
||||
// Store the argument values into `values_vec`.
|
||||
for (index, arg) in args.iter().enumerate() {
|
||||
unsafe {
|
||||
let ptr = values_vec.as_mut_ptr().add(index);
|
||||
|
||||
match arg {
|
||||
RuntimeValue::I32(x) => ptr::write(ptr as *mut i32, *x),
|
||||
RuntimeValue::I64(x) => ptr::write(ptr as *mut i64, *x),
|
||||
RuntimeValue::F32(x) => ptr::write(ptr as *mut u32, *x),
|
||||
RuntimeValue::F64(x) => ptr::write(ptr as *mut u64, *x),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Store the vmctx value into `values_vec`.
|
||||
unsafe {
|
||||
let ptr = values_vec.as_mut_ptr().add(args.len());
|
||||
ptr::write(ptr as *mut usize, vmctx as usize)
|
||||
}
|
||||
|
||||
// Get the trampoline to call for this function.
|
||||
let exec_code_buf =
|
||||
self.trampolines
|
||||
.get(code, isa, callee_address, &signature, value_size)?;
|
||||
|
||||
// Make all JIT code produced thus far executable.
|
||||
code.publish();
|
||||
|
||||
// Call the trampoline.
|
||||
if let Err(message) = unsafe {
|
||||
wasmtime_call_trampoline(
|
||||
exec_code_buf,
|
||||
values_vec.as_mut_ptr() as *mut u8,
|
||||
self.instance.vmctx_mut(),
|
||||
)
|
||||
} {
|
||||
return Ok(ActionOutcome::Trapped { message });
|
||||
}
|
||||
|
||||
// Load the return values out of `values_vec`.
|
||||
let values = signature
|
||||
.returns
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(index, abi_param)| unsafe {
|
||||
let ptr = values_vec.as_ptr().add(index);
|
||||
|
||||
match abi_param.value_type {
|
||||
ir::types::I32 => RuntimeValue::I32(ptr::read(ptr as *const i32)),
|
||||
ir::types::I64 => RuntimeValue::I64(ptr::read(ptr as *const i64)),
|
||||
ir::types::F32 => RuntimeValue::F32(ptr::read(ptr as *const u32)),
|
||||
ir::types::F64 => RuntimeValue::F64(ptr::read(ptr as *const u64)),
|
||||
other => panic!("unsupported value type {:?}", other),
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
|
||||
Ok(ActionOutcome::Returned { values })
|
||||
}
|
||||
|
||||
/// Read a global in this `InstanceWorld` by name.
|
||||
pub fn get(&self, global_name: &str) -> Result<RuntimeValue, ActionError> {
|
||||
let global_index = match self.module.exports.get(global_name) {
|
||||
Some(Export::Global(index)) => *index,
|
||||
Some(_) => {
|
||||
return Err(ActionError::Kind(format!(
|
||||
"exported item \"{}\" is not a global",
|
||||
global_name
|
||||
)))
|
||||
}
|
||||
None => {
|
||||
return Err(ActionError::Field(format!(
|
||||
"no export named \"{}\"",
|
||||
global_name
|
||||
)))
|
||||
}
|
||||
};
|
||||
|
||||
self.get_by_index(global_index)
|
||||
}
|
||||
|
||||
/// Reads the value of the indexed global variable in `module`.
|
||||
pub fn get_by_index(&self, global_index: GlobalIndex) -> Result<RuntimeValue, ActionError> {
|
||||
let global_address = match self.module.defined_global_index(global_index) {
|
||||
Some(def_global_index) => self
|
||||
.get_defined_global(def_global_index)
|
||||
.ok_or_else(|| ActionError::Index(def_global_index.index() as u64))?,
|
||||
None => {
|
||||
let from: *const VMGlobalDefinition = self
|
||||
.get_imported_global(global_index)
|
||||
.ok_or_else(|| ActionError::Index(global_index.index() as u64))?
|
||||
.from;
|
||||
from
|
||||
}
|
||||
};
|
||||
let global_def = unsafe { &*global_address };
|
||||
|
||||
unsafe {
|
||||
Ok(
|
||||
match self
|
||||
.module
|
||||
.globals
|
||||
.get(global_index)
|
||||
.ok_or_else(|| ActionError::Index(global_index.index() as u64))?
|
||||
.ty
|
||||
{
|
||||
ir::types::I32 => RuntimeValue::I32(*global_def.as_i32()),
|
||||
ir::types::I64 => RuntimeValue::I64(*global_def.as_i64()),
|
||||
ir::types::F32 => RuntimeValue::F32(*global_def.as_f32_bits()),
|
||||
ir::types::F64 => RuntimeValue::F64(*global_def.as_f64_bits()),
|
||||
other => {
|
||||
return Err(ActionError::Type(format!(
|
||||
"global with type {} not supported",
|
||||
other
|
||||
)))
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a slice of the contents of allocated linear memory.
|
||||
pub fn inspect_memory(
|
||||
&self,
|
||||
memory_index: MemoryIndex,
|
||||
address: usize,
|
||||
len: usize,
|
||||
) -> Result<&[u8], ActionError> {
|
||||
let memory_address = match self.module.defined_memory_index(memory_index) {
|
||||
Some(def_memory_index) => self
|
||||
.get_defined_memory(def_memory_index)
|
||||
.ok_or_else(|| ActionError::Index(def_memory_index.index() as u64))?,
|
||||
None => {
|
||||
let from: *const VMMemoryDefinition = self
|
||||
.get_imported_memory(memory_index)
|
||||
.ok_or_else(|| ActionError::Index(memory_index.index() as u64))?
|
||||
.from;
|
||||
from
|
||||
}
|
||||
};
|
||||
let memory_def = unsafe { &*memory_address };
|
||||
|
||||
Ok(unsafe {
|
||||
&slice::from_raw_parts(memory_def.base, memory_def.current_length)
|
||||
[address..address + len]
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn allocate_functions(
|
||||
code: &mut Code,
|
||||
compilation: Compilation,
|
||||
) -> Result<PrimaryMap<DefinedFuncIndex, *mut [VMFunctionBody]>, String> {
|
||||
let mut result = PrimaryMap::with_capacity(compilation.functions.len());
|
||||
for (_, body) in compilation.functions.into_iter() {
|
||||
let fatptr: *mut [VMFunctionBody] = code.allocate_copy_of_byte_slice(body)?;
|
||||
result.push(fatptr);
|
||||
}
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
struct TrampolinePark {
|
||||
/// Memorized per-function trampolines.
|
||||
memo: HashMap<*const VMFunctionBody, *const VMFunctionBody>,
|
||||
|
||||
/// The `FunctionBuilderContext`, shared between function compilations.
|
||||
fn_builder_ctx: FunctionBuilderContext,
|
||||
}
|
||||
|
||||
impl TrampolinePark {
|
||||
fn get(
|
||||
&mut self,
|
||||
code: &mut Code,
|
||||
isa: &isa::TargetIsa,
|
||||
callee_address: *const VMFunctionBody,
|
||||
signature: &ir::Signature,
|
||||
value_size: usize,
|
||||
) -> Result<*const VMFunctionBody, ActionError> {
|
||||
use std::collections::hash_map::Entry::{Occupied, Vacant};
|
||||
Ok(match self.memo.entry(callee_address) {
|
||||
Occupied(entry) => *entry.get(),
|
||||
Vacant(entry) => {
|
||||
let body = make_trampoline(
|
||||
&mut self.fn_builder_ctx,
|
||||
code,
|
||||
isa,
|
||||
callee_address,
|
||||
signature,
|
||||
value_size,
|
||||
)?;
|
||||
entry.insert(body);
|
||||
body
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn make_trampoline(
|
||||
fn_builder_ctx: &mut FunctionBuilderContext,
|
||||
code: &mut Code,
|
||||
isa: &isa::TargetIsa,
|
||||
callee_address: *const VMFunctionBody,
|
||||
signature: &ir::Signature,
|
||||
value_size: usize,
|
||||
) -> Result<*const VMFunctionBody, ActionError> {
|
||||
let pointer_type = isa.pointer_type();
|
||||
let mut wrapper_sig = ir::Signature::new(isa.frontend_config().default_call_conv);
|
||||
|
||||
// Add the `values_vec` parameter.
|
||||
wrapper_sig.params.push(ir::AbiParam::new(pointer_type));
|
||||
// Add the `vmctx` parameter.
|
||||
wrapper_sig.params.push(ir::AbiParam::special(
|
||||
pointer_type,
|
||||
ir::ArgumentPurpose::VMContext,
|
||||
));
|
||||
|
||||
let mut context = Context::new();
|
||||
context.func = ir::Function::with_name_signature(ir::ExternalName::user(0, 0), wrapper_sig);
|
||||
|
||||
{
|
||||
let mut builder = FunctionBuilder::new(&mut context.func, fn_builder_ctx);
|
||||
let block0 = builder.create_ebb();
|
||||
|
||||
builder.append_ebb_params_for_function_params(block0);
|
||||
builder.switch_to_block(block0);
|
||||
builder.seal_block(block0);
|
||||
|
||||
let mut callee_args = Vec::new();
|
||||
let pointer_type = isa.pointer_type();
|
||||
|
||||
let (values_vec_ptr_val, vmctx_ptr_val) = {
|
||||
let params = builder.func.dfg.ebb_params(block0);
|
||||
(params[0], params[1])
|
||||
};
|
||||
|
||||
// Load the argument values out of `values_vec`.
|
||||
let mflags = ir::MemFlags::trusted();
|
||||
for (i, r) in signature.params.iter().enumerate() {
|
||||
let value = match r.purpose {
|
||||
ir::ArgumentPurpose::Normal => builder.ins().load(
|
||||
r.value_type,
|
||||
mflags,
|
||||
values_vec_ptr_val,
|
||||
(i * value_size) as i32,
|
||||
),
|
||||
ir::ArgumentPurpose::VMContext => vmctx_ptr_val,
|
||||
other => panic!("unsupported argument purpose {}", other),
|
||||
};
|
||||
callee_args.push(value);
|
||||
}
|
||||
|
||||
let new_sig = builder.import_signature(signature.clone());
|
||||
|
||||
// TODO: It's possible to make this a direct call. We just need Cranelift
|
||||
// to support functions declared with an immediate integer address.
|
||||
// ExternalName::Absolute(u64). Let's do it.
|
||||
let callee_value = builder.ins().iconst(pointer_type, callee_address as i64);
|
||||
let call = builder
|
||||
.ins()
|
||||
.call_indirect(new_sig, callee_value, &callee_args);
|
||||
|
||||
let results = builder.func.dfg.inst_results(call).to_vec();
|
||||
|
||||
// Store the return values into `values_vec`.
|
||||
let mflags = ir::MemFlags::trusted();
|
||||
for (i, r) in results.iter().enumerate() {
|
||||
builder
|
||||
.ins()
|
||||
.store(mflags, *r, values_vec_ptr_val, (i * value_size) as i32);
|
||||
}
|
||||
|
||||
builder.ins().return_(&[]);
|
||||
builder.finalize()
|
||||
}
|
||||
|
||||
let mut code_buf: Vec<u8> = Vec::new();
|
||||
let mut reloc_sink = RelocSink::new();
|
||||
let mut trap_sink = binemit::NullTrapSink {};
|
||||
context
|
||||
.compile_and_emit(isa, &mut code_buf, &mut reloc_sink, &mut trap_sink)
|
||||
.map_err(|error| ActionError::Compile(CompileError::Codegen(error)))?;
|
||||
assert!(reloc_sink.func_relocs.is_empty());
|
||||
|
||||
Ok(code
|
||||
.allocate_copy_of_byte_slice(&code_buf)
|
||||
.map_err(ActionError::Resource)?
|
||||
.as_ptr())
|
||||
}
|
||||
@@ -1,22 +1,21 @@
|
||||
use cranelift_codegen::ir;
|
||||
use cranelift_wasm::Global;
|
||||
use wasmtime_environ::{MemoryPlan, TablePlan};
|
||||
use wasmtime_runtime::{
|
||||
use vmcontext::{
|
||||
VMContext, VMFunctionBody, VMGlobalDefinition, VMMemoryDefinition, VMTableDefinition,
|
||||
};
|
||||
|
||||
/// An exported function.
|
||||
pub struct FunctionExport {
|
||||
/// The address of the native-code function.
|
||||
pub address: *const VMFunctionBody,
|
||||
/// The function signature declaration, used for compatibilty checking.
|
||||
pub signature: ir::Signature,
|
||||
}
|
||||
use wasmtime_environ::{MemoryPlan, TablePlan};
|
||||
|
||||
/// The value of an export passed from one instance to another.
|
||||
pub enum Export {
|
||||
/// 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.
|
||||
Table {
|
||||
@@ -49,8 +48,16 @@ pub enum Export {
|
||||
|
||||
impl Export {
|
||||
/// Construct a function export value.
|
||||
pub fn function(address: *const VMFunctionBody, signature: ir::Signature) -> Self {
|
||||
Export::Function(FunctionExport { address, signature })
|
||||
pub fn function(
|
||||
address: *const VMFunctionBody,
|
||||
signature: ir::Signature,
|
||||
vmctx: *mut VMContext,
|
||||
) -> Self {
|
||||
Export::Function {
|
||||
address,
|
||||
signature,
|
||||
vmctx,
|
||||
}
|
||||
}
|
||||
|
||||
/// Construct a table export value.
|
||||
@@ -80,18 +87,3 @@ impl Export {
|
||||
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
|
||||
}
|
||||
}
|
||||
@@ -1,12 +1,12 @@
|
||||
use cranelift_entity::{BoxedSlice, PrimaryMap};
|
||||
use cranelift_wasm::{FuncIndex, GlobalIndex, MemoryIndex, TableIndex};
|
||||
use vmcontext::{VMFunctionBody, VMGlobalImport, VMMemoryImport, VMTableImport};
|
||||
use vmcontext::{VMFunctionImport, VMGlobalImport, VMMemoryImport, VMTableImport};
|
||||
|
||||
/// Resolved import pointers.
|
||||
#[derive(Debug)]
|
||||
pub struct Imports {
|
||||
/// Resolved addresses for imported functions.
|
||||
pub functions: BoxedSlice<FuncIndex, *const VMFunctionBody>,
|
||||
pub functions: BoxedSlice<FuncIndex, VMFunctionImport>,
|
||||
|
||||
/// Resolved addresses for imported tables.
|
||||
pub tables: BoxedSlice<TableIndex, VMTableImport>,
|
||||
@@ -21,7 +21,7 @@ pub struct Imports {
|
||||
impl Imports {
|
||||
/// Construct a new `Imports` instance.
|
||||
pub fn new(
|
||||
function_imports: PrimaryMap<FuncIndex, *const VMFunctionBody>,
|
||||
function_imports: PrimaryMap<FuncIndex, VMFunctionImport>,
|
||||
table_imports: PrimaryMap<TableIndex, VMTableImport>,
|
||||
memory_imports: PrimaryMap<MemoryIndex, VMMemoryImport>,
|
||||
global_imports: PrimaryMap<GlobalIndex, VMGlobalImport>,
|
||||
|
||||
@@ -4,13 +4,18 @@
|
||||
use cranelift_entity::EntityRef;
|
||||
use cranelift_entity::{BoxedSlice, PrimaryMap};
|
||||
use cranelift_wasm::{
|
||||
DefinedFuncIndex, DefinedGlobalIndex, DefinedMemoryIndex, DefinedTableIndex, FuncIndex,
|
||||
DefinedFuncIndex, DefinedGlobalIndex, DefinedMemoryIndex, DefinedTableIndex, GlobalInit,
|
||||
};
|
||||
use export::Export;
|
||||
use imports::Imports;
|
||||
use memory::LinearMemory;
|
||||
use sig_registry::SignatureRegistry;
|
||||
use signalhandlers::{wasmtime_init_eager, wasmtime_init_finish};
|
||||
use std::rc::Rc;
|
||||
use std::slice;
|
||||
use std::string::String;
|
||||
use table::Table;
|
||||
use traphandlers::wasmtime_call;
|
||||
use vmcontext::{
|
||||
VMCallerCheckedAnyfunc, VMContext, VMFunctionBody, VMGlobalDefinition, VMMemoryDefinition,
|
||||
VMTableDefinition,
|
||||
@@ -20,6 +25,9 @@ use wasmtime_environ::{DataInitializer, Module};
|
||||
/// An Instance of a WebAssemby module.
|
||||
#[derive(Debug)]
|
||||
pub struct Instance {
|
||||
/// The `Module` this `Instance` was instantiated from.
|
||||
module: Rc<Module>,
|
||||
|
||||
/// WebAssembly linear memory data.
|
||||
memories: BoxedSlice<DefinedMemoryIndex, LinearMemory>,
|
||||
|
||||
@@ -33,6 +41,9 @@ pub struct Instance {
|
||||
/// Resolved 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.
|
||||
vmctx_tables: BoxedSlice<DefinedTableIndex, VMTableDefinition>,
|
||||
|
||||
@@ -49,19 +60,20 @@ pub struct Instance {
|
||||
impl Instance {
|
||||
/// Create a new `Instance`.
|
||||
pub fn new(
|
||||
module: &Module,
|
||||
finished_functions: &BoxedSlice<DefinedFuncIndex, *const VMFunctionBody>,
|
||||
module: Rc<Module>,
|
||||
finished_functions: BoxedSlice<DefinedFuncIndex, *const VMFunctionBody>,
|
||||
mut vmctx_imports: Imports,
|
||||
data_initializers: &[DataInitializer],
|
||||
) -> Result<Self, String> {
|
||||
let mut sig_registry = instantiate_signatures(module);
|
||||
let mut memories = instantiate_memories(module, data_initializers)?;
|
||||
let mut tables = instantiate_tables(
|
||||
module,
|
||||
finished_functions,
|
||||
&vmctx_imports.functions,
|
||||
&mut sig_registry,
|
||||
);
|
||||
data_initializers: Vec<DataInitializer>,
|
||||
) -> Result<Box<Self>, InstantiationError> {
|
||||
let mut sig_registry = create_and_initialize_signatures(&module);
|
||||
let mut tables = create_tables(&module);
|
||||
let mut memories = create_memories(&module)?;
|
||||
|
||||
let mut vmctx_tables = tables
|
||||
.values_mut()
|
||||
.map(Table::vmtable)
|
||||
.collect::<PrimaryMap<DefinedTableIndex, _>>()
|
||||
.into_boxed_slice();
|
||||
|
||||
let mut vmctx_memories = memories
|
||||
.values_mut()
|
||||
@@ -69,13 +81,7 @@ impl Instance {
|
||||
.collect::<PrimaryMap<DefinedMemoryIndex, _>>()
|
||||
.into_boxed_slice();
|
||||
|
||||
let mut vmctx_globals = instantiate_globals(module);
|
||||
|
||||
let mut vmctx_tables = tables
|
||||
.values_mut()
|
||||
.map(Table::vmtable)
|
||||
.collect::<PrimaryMap<DefinedTableIndex, _>>()
|
||||
.into_boxed_slice();
|
||||
let mut vmctx_globals = create_globals(&module);
|
||||
|
||||
let vmctx_imported_functions_ptr = vmctx_imports
|
||||
.functions
|
||||
@@ -90,19 +96,21 @@ impl Instance {
|
||||
.as_mut_ptr();
|
||||
let vmctx_imported_globals_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_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();
|
||||
|
||||
Ok(Self {
|
||||
let mut result = Box::new(Self {
|
||||
module,
|
||||
memories,
|
||||
tables,
|
||||
sig_registry,
|
||||
vmctx_imports,
|
||||
finished_functions,
|
||||
vmctx_tables,
|
||||
vmctx_memories,
|
||||
vmctx_globals,
|
||||
vmctx_tables,
|
||||
vmctx: VMContext::new(
|
||||
vmctx_imported_functions_ptr,
|
||||
vmctx_imported_tables_ptr,
|
||||
@@ -113,7 +121,31 @@ impl Instance {
|
||||
vmctx_globals_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.
|
||||
@@ -121,11 +153,21 @@ impl Instance {
|
||||
&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.
|
||||
pub fn vmctx_mut(&mut self) -> &mut VMContext {
|
||||
&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.
|
||||
pub(crate) fn vmctx_offset() -> isize {
|
||||
offset_of!(Self, vmctx) as isize
|
||||
@@ -166,11 +208,195 @@ impl Instance {
|
||||
|
||||
/// Return the number of imported memories.
|
||||
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();
|
||||
for (sig_index, sig) in module.signatures.iter() {
|
||||
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.
|
||||
fn instantiate_tables(
|
||||
module: &Module,
|
||||
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();
|
||||
fn create_tables(module: &Module) -> BoxedSlice<DefinedTableIndex, Table> {
|
||||
let num_imports = module.imported_tables.len();
|
||||
let mut tables: PrimaryMap<DefinedTableIndex, _> =
|
||||
PrimaryMap::with_capacity(module.table_plans.len() - num_imports);
|
||||
for table in &module.table_plans.values().as_slice()[num_imports..] {
|
||||
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()
|
||||
}
|
||||
|
||||
/// 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.
|
||||
fn instantiate_memories(
|
||||
fn create_memories(
|
||||
module: &Module,
|
||||
data_initializers: &[DataInitializer],
|
||||
) -> Result<BoxedSlice<DefinedMemoryIndex, LinearMemory>, String> {
|
||||
) -> Result<BoxedSlice<DefinedMemoryIndex, LinearMemory>, InstantiationError> {
|
||||
let num_imports = module.imported_memories.len();
|
||||
let mut memories: PrimaryMap<DefinedMemoryIndex, _> =
|
||||
PrimaryMap::with_capacity(module.memory_plans.len() - 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())
|
||||
}
|
||||
|
||||
/// 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,
|
||||
/// without any initializers applied yet.
|
||||
fn instantiate_globals(module: &Module) -> BoxedSlice<DefinedGlobalIndex, VMGlobalDefinition> {
|
||||
/// with initializers applied.
|
||||
fn create_globals(module: &Module) -> BoxedSlice<DefinedGlobalIndex, VMGlobalDefinition> {
|
||||
let num_imports = module.imported_globals.len();
|
||||
let mut vmctx_globals = PrimaryMap::with_capacity(module.globals.len() - num_imports);
|
||||
|
||||
for global in &module.globals.values().as_slice()[num_imports..] {
|
||||
vmctx_globals.push(VMGlobalDefinition::new(global));
|
||||
for _ in &module.globals.values().as_slice()[num_imports..] {
|
||||
vmctx_globals.push(VMGlobalDefinition::new());
|
||||
}
|
||||
|
||||
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),
|
||||
}
|
||||
|
||||
@@ -39,7 +39,11 @@ extern crate libc;
|
||||
#[macro_use]
|
||||
extern crate memoffset;
|
||||
extern crate cast;
|
||||
extern crate failure;
|
||||
#[macro_use]
|
||||
extern crate failure_derive;
|
||||
|
||||
mod export;
|
||||
mod imports;
|
||||
mod instance;
|
||||
mod memory;
|
||||
@@ -52,14 +56,15 @@ mod vmcontext;
|
||||
|
||||
pub mod libcalls;
|
||||
|
||||
pub use export::Export;
|
||||
pub use imports::Imports;
|
||||
pub use instance::Instance;
|
||||
pub use instance::{Instance, InstantiationError};
|
||||
pub use mmap::Mmap;
|
||||
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::{
|
||||
VMContext, VMFunctionBody, VMGlobalDefinition, VMGlobalImport, VMMemoryDefinition,
|
||||
VMMemoryImport, VMTableDefinition, VMTableImport,
|
||||
VMContext, VMFunctionBody, VMFunctionImport, VMGlobalDefinition, VMGlobalImport,
|
||||
VMMemoryDefinition, VMMemoryImport, VMTableDefinition, VMTableImport,
|
||||
};
|
||||
|
||||
#[cfg(not(feature = "std"))]
|
||||
|
||||
@@ -116,7 +116,7 @@ pub unsafe extern "C" fn wasmtime_imported_memory32_grow(
|
||||
);
|
||||
|
||||
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_memory = &mut *import.from;
|
||||
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 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_memory = &mut *import.from;
|
||||
let foreign_index = foreign_instance.vmctx().memory_index(foreign_memory);
|
||||
|
||||
@@ -65,6 +65,7 @@ impl LinearMemory {
|
||||
let mmap = Mmap::with_size(request_bytes)?;
|
||||
|
||||
// Make the unmapped and offset-guard pages inaccessible.
|
||||
if request_bytes != 0 {
|
||||
unsafe {
|
||||
region::protect(
|
||||
mmap.as_ptr().add(mapped_bytes),
|
||||
@@ -73,6 +74,7 @@ impl LinearMemory {
|
||||
)
|
||||
}
|
||||
.expect("unable to make memory inaccessible");
|
||||
}
|
||||
|
||||
Ok(Self {
|
||||
mmap,
|
||||
@@ -150,19 +152,7 @@ impl LinearMemory {
|
||||
pub fn vmmemory(&mut self) -> VMMemoryDefinition {
|
||||
VMMemoryDefinition {
|
||||
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()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,6 +34,12 @@ impl Mmap {
|
||||
/// suitably sized and aligned for memory protection.
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
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 alloc_size = round_up_to_page_size(size, page_size);
|
||||
let ptr = unsafe {
|
||||
|
||||
@@ -107,3 +107,27 @@ pub unsafe extern "C" fn wasmtime_call_trampoline(
|
||||
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(())
|
||||
})
|
||||
}
|
||||
|
||||
@@ -3,12 +3,47 @@
|
||||
|
||||
use cranelift_entity::EntityRef;
|
||||
use cranelift_wasm::{
|
||||
DefinedGlobalIndex, DefinedMemoryIndex, DefinedTableIndex, FuncIndex, Global, GlobalIndex,
|
||||
GlobalInit, MemoryIndex, TableIndex,
|
||||
DefinedGlobalIndex, DefinedMemoryIndex, DefinedTableIndex, FuncIndex, GlobalIndex, MemoryIndex,
|
||||
TableIndex,
|
||||
};
|
||||
use instance::Instance;
|
||||
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
|
||||
/// 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
|
||||
@@ -244,17 +279,8 @@ mod test_vmglobal_definition {
|
||||
|
||||
impl VMGlobalDefinition {
|
||||
/// Construct a `VMGlobalDefinition`.
|
||||
pub fn new(global: &Global) -> Self {
|
||||
let mut result = 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
|
||||
pub fn new() -> Self {
|
||||
Self { storage: [0; 8] }
|
||||
}
|
||||
|
||||
/// Return a reference to the value as an i32.
|
||||
@@ -366,6 +392,7 @@ impl VMSharedSignatureIndex {
|
||||
pub struct VMCallerCheckedAnyfunc {
|
||||
pub func_ptr: *const VMFunctionBody,
|
||||
pub type_index: VMSharedSignatureIndex,
|
||||
pub vmctx: *mut VMContext,
|
||||
// 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),
|
||||
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 {
|
||||
func_ptr: ptr::null_mut(),
|
||||
type_index: VMSharedSignatureIndex::new(u32::MAX),
|
||||
vmctx: ptr::null_mut(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -413,16 +445,16 @@ impl Default for VMCallerCheckedAnyfunc {
|
||||
#[repr(C)]
|
||||
pub struct VMContext {
|
||||
/// 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`.
|
||||
imported_tables: *mut VMTableImport,
|
||||
imported_tables: *const VMTableImport,
|
||||
|
||||
/// 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`.
|
||||
imported_globals: *mut VMGlobalImport,
|
||||
imported_globals: *const VMGlobalImport,
|
||||
|
||||
/// A pointer to an array of locally-defined `VMTableDefinition` instances,
|
||||
/// indexed by `DefinedTableIndex`.
|
||||
@@ -473,10 +505,10 @@ mod test {
|
||||
impl VMContext {
|
||||
/// Create a new `VMContext` instance.
|
||||
pub fn new(
|
||||
imported_functions: *const *const VMFunctionBody,
|
||||
imported_tables: *mut VMTableImport,
|
||||
imported_memories: *mut VMMemoryImport,
|
||||
imported_globals: *mut VMGlobalImport,
|
||||
imported_functions: *const VMFunctionImport,
|
||||
imported_tables: *const VMTableImport,
|
||||
imported_memories: *const VMMemoryImport,
|
||||
imported_globals: *const VMGlobalImport,
|
||||
tables: *mut VMTableDefinition,
|
||||
memories: *mut VMMemoryDefinition,
|
||||
globals: *mut VMGlobalDefinition,
|
||||
@@ -495,8 +527,8 @@ impl VMContext {
|
||||
}
|
||||
|
||||
/// Return a reference to imported function `index`.
|
||||
pub unsafe fn imported_function(&self, index: FuncIndex) -> *const VMFunctionBody {
|
||||
*self.imported_functions.add(index.index())
|
||||
pub unsafe fn imported_function(&self, index: FuncIndex) -> &VMFunctionImport {
|
||||
&*self.imported_functions.add(index.index())
|
||||
}
|
||||
|
||||
/// Return a reference to imported table `index`.
|
||||
@@ -504,31 +536,16 @@ impl VMContext {
|
||||
&*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`.
|
||||
pub unsafe fn imported_memory(&self, index: MemoryIndex) -> &VMMemoryImport {
|
||||
&*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`.
|
||||
pub unsafe fn imported_global(&self, index: GlobalIndex) -> &VMGlobalImport {
|
||||
&*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`.
|
||||
pub unsafe fn table(&self, index: DefinedTableIndex) -> &VMTableDefinition {
|
||||
&*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)
|
||||
}
|
||||
|
||||
/// 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`.
|
||||
pub fn memory_index(&self, memory: &mut VMMemoryDefinition) -> DefinedMemoryIndex {
|
||||
// TODO: Use `offset_from` once it stablizes.
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
@@ -1,16 +1,14 @@
|
||||
use cranelift_codegen::ir::types;
|
||||
use cranelift_codegen::{ir, isa};
|
||||
use cranelift_entity::PrimaryMap;
|
||||
use cranelift_wasm::{Global, GlobalInit, Memory, Table, TableElementType};
|
||||
use std::ptr;
|
||||
use cranelift_wasm::{DefinedFuncIndex, Global, GlobalInit, Memory, Table, TableElementType};
|
||||
use std::rc::Rc;
|
||||
use target_lexicon::HOST;
|
||||
use wasmtime_environ::{
|
||||
translate_signature, MemoryPlan, MemoryStyle, Module, TablePlan, TableStyle,
|
||||
};
|
||||
use wasmtime_execute::{Export, Resolver};
|
||||
use wasmtime_runtime::{
|
||||
Imports, Instance, VMFunctionBody, VMGlobalDefinition, VMMemoryDefinition, VMTableDefinition,
|
||||
translate_signature, Export, MemoryPlan, MemoryStyle, Module, TablePlan, TableStyle,
|
||||
};
|
||||
use wasmtime_execute::{ActionError, InstancePlus};
|
||||
use wasmtime_runtime::{Imports, VMFunctionBody};
|
||||
|
||||
extern "C" fn spectest_print() {}
|
||||
|
||||
@@ -46,195 +44,181 @@ extern "C" fn spectest_print_f64_f64(x: f64, y: f64) {
|
||||
println!("{}: f64", y);
|
||||
}
|
||||
|
||||
pub struct SpecTest {
|
||||
instance: Instance,
|
||||
spectest_global_i32: VMGlobalDefinition,
|
||||
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> {
|
||||
/// Return an instance implementing the "spectest" interface used in the
|
||||
/// spec testsuite.
|
||||
pub fn instantiate_spectest() -> Result<InstancePlus, ActionError> {
|
||||
let call_conv = isa::CallConv::triple_default(&HOST);
|
||||
let pointer_type = types::Type::triple_pointer_type(&HOST);
|
||||
match module {
|
||||
"spectest" => match field {
|
||||
"print" => Some(Export::function(
|
||||
spectest_print as *const VMFunctionBody,
|
||||
translate_signature(
|
||||
let mut module = Module::new();
|
||||
let mut finished_functions: PrimaryMap<DefinedFuncIndex, *const VMFunctionBody> =
|
||||
PrimaryMap::new();
|
||||
|
||||
let sig = module.signatures.push(translate_signature(
|
||||
ir::Signature {
|
||||
params: vec![],
|
||||
returns: vec![],
|
||||
call_conv,
|
||||
},
|
||||
pointer_type,
|
||||
),
|
||||
)),
|
||||
"print_i32" => Some(Export::function(
|
||||
spectest_print_i32 as *const VMFunctionBody,
|
||||
translate_signature(
|
||||
));
|
||||
let func = module.functions.push(sig);
|
||||
module
|
||||
.exports
|
||||
.insert("print".to_owned(), Export::Function(func));
|
||||
finished_functions.push(spectest_print as *const VMFunctionBody);
|
||||
|
||||
let sig = module.signatures.push(translate_signature(
|
||||
ir::Signature {
|
||||
params: vec![ir::AbiParam::new(types::I32)],
|
||||
returns: vec![],
|
||||
call_conv,
|
||||
},
|
||||
pointer_type,
|
||||
),
|
||||
)),
|
||||
"print_i64" => Some(Export::function(
|
||||
spectest_print_i64 as *const VMFunctionBody,
|
||||
translate_signature(
|
||||
));
|
||||
let func = module.functions.push(sig);
|
||||
module
|
||||
.exports
|
||||
.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 {
|
||||
params: vec![ir::AbiParam::new(types::I64)],
|
||||
returns: vec![],
|
||||
call_conv,
|
||||
},
|
||||
pointer_type,
|
||||
),
|
||||
)),
|
||||
"print_f32" => Some(Export::function(
|
||||
spectest_print_f32 as *const VMFunctionBody,
|
||||
translate_signature(
|
||||
));
|
||||
let func = module.functions.push(sig);
|
||||
module
|
||||
.exports
|
||||
.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 {
|
||||
params: vec![ir::AbiParam::new(types::F32)],
|
||||
returns: vec![],
|
||||
call_conv,
|
||||
},
|
||||
pointer_type,
|
||||
),
|
||||
)),
|
||||
"print_f64" => Some(Export::function(
|
||||
spectest_print_f64 as *const VMFunctionBody,
|
||||
translate_signature(
|
||||
));
|
||||
let func = module.functions.push(sig);
|
||||
module
|
||||
.exports
|
||||
.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 {
|
||||
params: vec![ir::AbiParam::new(types::F64)],
|
||||
returns: vec![],
|
||||
call_conv,
|
||||
},
|
||||
pointer_type,
|
||||
),
|
||||
)),
|
||||
"print_i32_f32" => Some(Export::function(
|
||||
spectest_print_i32_f32 as *const VMFunctionBody,
|
||||
translate_signature(
|
||||
));
|
||||
let func = module.functions.push(sig);
|
||||
module
|
||||
.exports
|
||||
.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 {
|
||||
params: vec![
|
||||
ir::AbiParam::new(types::I32),
|
||||
ir::AbiParam::new(types::F32),
|
||||
],
|
||||
params: vec![ir::AbiParam::new(types::I32), ir::AbiParam::new(types::F32)],
|
||||
returns: vec![],
|
||||
call_conv,
|
||||
},
|
||||
pointer_type,
|
||||
),
|
||||
)),
|
||||
"print_f64_f64" => Some(Export::function(
|
||||
spectest_print_f64_f64 as *const VMFunctionBody,
|
||||
translate_signature(
|
||||
));
|
||||
let func = module.functions.push(sig);
|
||||
module
|
||||
.exports
|
||||
.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 {
|
||||
params: vec![
|
||||
ir::AbiParam::new(types::F64),
|
||||
ir::AbiParam::new(types::F64),
|
||||
],
|
||||
params: vec![ir::AbiParam::new(types::F64), ir::AbiParam::new(types::F64)],
|
||||
returns: vec![],
|
||||
call_conv,
|
||||
},
|
||||
pointer_type,
|
||||
),
|
||||
)),
|
||||
"global_i32" => Some(Export::global(
|
||||
&mut self.spectest_global_i32,
|
||||
Global {
|
||||
ty: ir::types::I32,
|
||||
));
|
||||
let func = module.functions.push(sig);
|
||||
module
|
||||
.exports
|
||||
.insert("print_f64_f64".to_owned(), Export::Function(func));
|
||||
finished_functions.push(spectest_print_f64_f64 as *const VMFunctionBody);
|
||||
|
||||
let global = module.globals.push(Global {
|
||||
ty: types::I32,
|
||||
mutability: false,
|
||||
initializer: GlobalInit::I32Const(0),
|
||||
},
|
||||
)),
|
||||
"global_f32" => Some(Export::global(
|
||||
&mut self.spectest_global_f32,
|
||||
Global {
|
||||
ty: ir::types::F32,
|
||||
initializer: GlobalInit::I32Const(666),
|
||||
});
|
||||
module
|
||||
.exports
|
||||
.insert("global_i32".to_owned(), Export::Global(global));
|
||||
|
||||
let global = module.globals.push(Global {
|
||||
ty: types::I64,
|
||||
mutability: false,
|
||||
initializer: GlobalInit::F32Const(0),
|
||||
},
|
||||
)),
|
||||
"global_f64" => Some(Export::global(
|
||||
&mut self.spectest_global_f64,
|
||||
Global {
|
||||
ty: ir::types::F64,
|
||||
initializer: GlobalInit::I64Const(666),
|
||||
});
|
||||
module
|
||||
.exports
|
||||
.insert("global_i64".to_owned(), Export::Global(global));
|
||||
|
||||
let global = module.globals.push(Global {
|
||||
ty: types::F32,
|
||||
mutability: false,
|
||||
initializer: GlobalInit::F64Const(0),
|
||||
},
|
||||
)),
|
||||
"table" => Some(Export::table(
|
||||
&mut self.spectest_table,
|
||||
self.instance.vmctx_mut(),
|
||||
TablePlan {
|
||||
initializer: GlobalInit::F32Const(0x44268000),
|
||||
});
|
||||
module
|
||||
.exports
|
||||
.insert("global_f32".to_owned(), Export::Global(global));
|
||||
|
||||
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 {
|
||||
ty: TableElementType::Func,
|
||||
minimum: 0,
|
||||
maximum: None,
|
||||
minimum: 10,
|
||||
maximum: Some(20),
|
||||
},
|
||||
style: TableStyle::CallerChecksSignature,
|
||||
},
|
||||
)),
|
||||
"memory" => Some(Export::memory(
|
||||
&mut self.spectest_memory,
|
||||
self.instance.vmctx_mut(),
|
||||
MemoryPlan {
|
||||
});
|
||||
module
|
||||
.exports
|
||||
.insert("table".to_owned(), Export::Table(table));
|
||||
|
||||
let memory = module.memory_plans.push(MemoryPlan {
|
||||
memory: Memory {
|
||||
minimum: 0,
|
||||
maximum: None,
|
||||
minimum: 1,
|
||||
maximum: Some(2),
|
||||
shared: false,
|
||||
},
|
||||
style: MemoryStyle::Dynamic,
|
||||
offset_guard_size: 0,
|
||||
},
|
||||
)),
|
||||
_ => None,
|
||||
},
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
style: MemoryStyle::Static { bound: 65536 },
|
||||
offset_guard_size: 0x80000000,
|
||||
});
|
||||
module
|
||||
.exports
|
||||
.insert("memory".to_owned(), Export::Memory(memory));
|
||||
|
||||
let imports = Imports::none();
|
||||
let data_initializers = Vec::new();
|
||||
|
||||
InstancePlus::with_parts(
|
||||
Rc::new(module),
|
||||
finished_functions.into_boxed_slice(),
|
||||
imports,
|
||||
data_initializers,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
use cranelift_codegen::isa;
|
||||
use cranelift_entity::PrimaryMap;
|
||||
use spectest::SpecTest;
|
||||
use spectest::instantiate_spectest;
|
||||
use std::collections::HashMap;
|
||||
use std::io::Read;
|
||||
use std::path::Path;
|
||||
use std::{fmt, fs, io, str};
|
||||
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.
|
||||
fn runtime_value(v: Value) -> RuntimeValue {
|
||||
@@ -72,45 +73,70 @@ pub struct WastFileError {
|
||||
error: WastError,
|
||||
}
|
||||
|
||||
/// An opaque reference to an `InstanceWorld`.
|
||||
/// An opaque reference to an `InstancePlus`.
|
||||
#[derive(Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
|
||||
pub struct WorldIndex(u32);
|
||||
entity_impl!(WorldIndex, "world");
|
||||
pub struct InstancePlusIndex(u32);
|
||||
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
|
||||
/// to be performed on them.
|
||||
pub struct WastContext {
|
||||
/// A namespace of wasm modules, keyed by an optional name.
|
||||
worlds: PrimaryMap<WorldIndex, InstanceWorld>,
|
||||
current: Option<WorldIndex>,
|
||||
namespace: HashMap<String, WorldIndex>,
|
||||
code: Code,
|
||||
spectest: SpecTest,
|
||||
current: Option<InstancePlusIndex>,
|
||||
namespace: WasmNamespace,
|
||||
jit_code: JITCode,
|
||||
}
|
||||
|
||||
impl WastContext {
|
||||
/// Construct a new instance of `WastContext`.
|
||||
pub fn new() -> Result<Self, String> {
|
||||
Ok(Self {
|
||||
worlds: PrimaryMap::new(),
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
current: None,
|
||||
namespace: HashMap::new(),
|
||||
code: Code::new(),
|
||||
spectest: SpecTest::new()?,
|
||||
})
|
||||
namespace: WasmNamespace::new(),
|
||||
jit_code: JITCode::new(),
|
||||
}
|
||||
}
|
||||
|
||||
fn instantiate(
|
||||
&mut self,
|
||||
isa: &isa::TargetIsa,
|
||||
module: ModuleBinary,
|
||||
) -> Result<InstanceWorld, ActionError> {
|
||||
InstanceWorld::new(&mut self.code, isa, &module.into_vec(), &mut self.spectest)
|
||||
) -> Result<InstancePlus, ActionError> {
|
||||
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 {
|
||||
self.namespace.get_mut(name).ok_or_else(|| {
|
||||
self.namespace.names.get_mut(name).ok_or_else(|| {
|
||||
WastError::Module(UnknownModule {
|
||||
module: Some(name.to_owned()),
|
||||
})
|
||||
@@ -124,6 +150,14 @@ impl WastContext {
|
||||
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.
|
||||
pub fn module(
|
||||
&mut self,
|
||||
@@ -131,21 +165,18 @@ impl WastContext {
|
||||
name: Option<String>,
|
||||
module: ModuleBinary,
|
||||
) -> Result<(), ActionError> {
|
||||
let world = self.instantiate(isa, module)?;
|
||||
let index = if let Some(name) = name {
|
||||
self.register(name, world)
|
||||
} else {
|
||||
self.worlds.push(world)
|
||||
};
|
||||
let instance = self.instantiate(isa, module)?;
|
||||
let index = self.namespace.instances.push(instance);
|
||||
if let Some(name) = name {
|
||||
self.register(name, index);
|
||||
}
|
||||
self.current = Some(index);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Register a module to make it available for performing actions.
|
||||
pub fn register(&mut self, name: String, world: InstanceWorld) -> WorldIndex {
|
||||
let index = self.worlds.push(world);
|
||||
self.namespace.insert(name, index);
|
||||
index
|
||||
pub fn register(&mut self, name: String, index: InstancePlusIndex) {
|
||||
self.namespace.names.insert(name, index);
|
||||
}
|
||||
|
||||
/// Invoke an exported function from a defined module.
|
||||
@@ -160,16 +191,18 @@ impl WastContext {
|
||||
for arg in args {
|
||||
value_args.push(runtime_value(*arg));
|
||||
}
|
||||
let index = self.get_world(&module)?;
|
||||
self.worlds[index]
|
||||
.invoke(&mut self.code, isa, &field, &value_args)
|
||||
let index = self.get_instance(&module)?;
|
||||
self.namespace.instances[index]
|
||||
.invoke(&mut self.jit_code, isa, &field, &value_args)
|
||||
.map_err(WastError::Action)
|
||||
}
|
||||
|
||||
/// Get the value of an exported global from a defined module.
|
||||
pub fn get(&mut self, module: Option<String>, field: &str) -> Result<RuntimeValue, WastError> {
|
||||
let index = self.get_world(&module)?;
|
||||
self.worlds[index].get(&field).map_err(WastError::Action)
|
||||
let index = self.get_instance(&module)?;
|
||||
self.namespace.instances[index]
|
||||
.get(&field)
|
||||
.map_err(WastError::Action)
|
||||
}
|
||||
|
||||
fn perform_action(
|
||||
@@ -211,11 +244,13 @@ impl WastContext {
|
||||
error: WastError::Action(error),
|
||||
})?;
|
||||
}
|
||||
CommandKind::Register {
|
||||
name: _name,
|
||||
as_name: _as_name,
|
||||
} => {
|
||||
println!("{}:{}: TODO: Implement register", filename, line);
|
||||
CommandKind::Register { name, as_name } => {
|
||||
let index = self.get_instance(&name).map_err(|error| WastFileError {
|
||||
filename: filename.to_string(),
|
||||
line,
|
||||
error,
|
||||
})?;
|
||||
self.register(as_name, index);
|
||||
}
|
||||
CommandKind::PerformAction(action) => match self
|
||||
.perform_action(isa, action)
|
||||
|
||||
@@ -31,9 +31,7 @@
|
||||
)]
|
||||
|
||||
extern crate cranelift_codegen;
|
||||
extern crate cranelift_entity;
|
||||
extern crate cranelift_native;
|
||||
extern crate cranelift_wasm;
|
||||
extern crate docopt;
|
||||
extern crate wasmtime_execute;
|
||||
#[macro_use]
|
||||
@@ -45,18 +43,15 @@ extern crate wabt;
|
||||
use cranelift_codegen::isa::TargetIsa;
|
||||
use cranelift_codegen::settings;
|
||||
use cranelift_codegen::settings::Configurable;
|
||||
use cranelift_entity::EntityRef;
|
||||
use cranelift_wasm::MemoryIndex;
|
||||
use docopt::Docopt;
|
||||
use std::error::Error;
|
||||
use std::fs::File;
|
||||
use std::io;
|
||||
use std::io::prelude::*;
|
||||
use std::io::stdout;
|
||||
use std::path::Path;
|
||||
use std::path::PathBuf;
|
||||
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.";
|
||||
|
||||
@@ -68,14 +63,13 @@ including calling the start function if one is present. Additional functions
|
||||
given with --invoke are then called.
|
||||
|
||||
Usage:
|
||||
wasmtime [-omd] <file>...
|
||||
wasmtime [-omd] <file>... --invoke=<fn>
|
||||
wasmtime [-od] <file>...
|
||||
wasmtime [-od] <file>... --invoke=<fn>
|
||||
wasmtime --help | --version
|
||||
|
||||
Options:
|
||||
--invoke=<fn> name of function to run
|
||||
-o, --optimize runs optimization passes on the translated functions
|
||||
-m, --memory interactive memory inspector after execution
|
||||
-d, --debug enable debug output on stderr/stdout
|
||||
-h, --help print this help message
|
||||
--version print the Cranelift version
|
||||
@@ -84,7 +78,6 @@ Options:
|
||||
#[derive(Deserialize, Debug, Clone)]
|
||||
struct Args {
|
||||
arg_file: Vec<String>,
|
||||
flag_memory: bool,
|
||||
flag_optimize: bool,
|
||||
flag_debug: bool,
|
||||
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()))?;
|
||||
}
|
||||
let mut resolver = NullResolver {};
|
||||
let mut code = Code::new();
|
||||
let mut world =
|
||||
InstanceWorld::new(&mut code, isa, &data, &mut resolver).map_err(|e| e.to_string())?;
|
||||
let mut jit_code = JITCode::new();
|
||||
let mut instance_plus =
|
||||
InstancePlus::new(&mut jit_code, isa, &data, &mut resolver).map_err(|e| e.to_string())?;
|
||||
|
||||
if let Some(ref f) = args.flag_invoke {
|
||||
match world
|
||||
.invoke(&mut code, isa, &f, &[])
|
||||
match instance_plus
|
||||
.invoke(&mut jit_code, isa, &f, &[])
|
||||
.map_err(|e| e.to_string())?
|
||||
{
|
||||
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(())
|
||||
}
|
||||
|
||||
@@ -211,7 +168,7 @@ mod tests {
|
||||
use cranelift_codegen::settings::Configurable;
|
||||
use std::path::PathBuf;
|
||||
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";
|
||||
|
||||
@@ -234,8 +191,8 @@ mod tests {
|
||||
let isa = isa_builder.finish(settings::Flags::new(flag_builder));
|
||||
|
||||
let mut resolver = NullResolver {};
|
||||
let mut code = Code::new();
|
||||
let world = InstanceWorld::new(&mut code, &*isa, &data, &mut resolver);
|
||||
assert!(world.is_ok());
|
||||
let mut code = JITCode::new();
|
||||
let instance = InstancePlus::new(&mut code, &*isa, &data, &mut resolver);
|
||||
assert!(instance.is_ok());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -94,7 +94,12 @@ fn main() {
|
||||
}
|
||||
|
||||
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 {
|
||||
wast_context
|
||||
.run_file(&*isa, Path::new(&filename))
|
||||
@@ -8,7 +8,7 @@ use cranelift_codegen::settings::Configurable;
|
||||
use std::path::Path;
|
||||
use wasmtime_wast::WastContext;
|
||||
|
||||
include!(concat!(env!("OUT_DIR"), "/run_wast_files.rs"));
|
||||
include!(concat!(env!("OUT_DIR"), "/wast_testsuite_tests.rs"));
|
||||
|
||||
#[cfg(test)]
|
||||
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(|_| {
|
||||
panic!("host machine is not a supported target");
|
||||
});
|
||||
|
||||
isa_builder.finish(settings::Flags::new(flag_builder))
|
||||
}
|
||||
Reference in New Issue
Block a user