Refactor the compilation and instantiation pipeline.
wasmtime-execute is now wasmtime-jit. Move `JITCode` and the TargetIsa into a new `Compiler` type. `InstancePlus` is no more, with trampoline functionality now handled by `Compiler`.
This commit is contained in:
@@ -28,7 +28,7 @@ cranelift-entity = "0.26.0"
|
|||||||
cranelift-wasm = "0.26.0"
|
cranelift-wasm = "0.26.0"
|
||||||
wasmtime-environ = { path = "lib/environ" }
|
wasmtime-environ = { path = "lib/environ" }
|
||||||
wasmtime-runtime = { path = "lib/runtime" }
|
wasmtime-runtime = { path = "lib/runtime" }
|
||||||
wasmtime-execute = { path = "lib/execute" }
|
wasmtime-jit = { path = "lib/jit" }
|
||||||
wasmtime-obj = { path = "lib/obj" }
|
wasmtime-obj = { path = "lib/obj" }
|
||||||
wasmtime-wast = { path = "lib/wast" }
|
wasmtime-wast = { path = "lib/wast" }
|
||||||
docopt = "1.0.1"
|
docopt = "1.0.1"
|
||||||
|
|||||||
13
build.rs
13
build.rs
@@ -53,7 +53,10 @@ fn test_directory(out: &mut File, testsuite: &str) -> io::Result<()> {
|
|||||||
.expect("testsuite filename should be representable as a string")
|
.expect("testsuite filename should be representable as a string")
|
||||||
.replace("-", "_")
|
.replace("-", "_")
|
||||||
)?;
|
)?;
|
||||||
writeln!(out, " use super::{{native_isa, Path, WastContext}};")?;
|
writeln!(
|
||||||
|
out,
|
||||||
|
" use super::{{native_isa, Path, WastContext, Compiler}};"
|
||||||
|
)?;
|
||||||
for dir_entry in dir_entries {
|
for dir_entry in dir_entries {
|
||||||
write_testsuite_tests(out, dir_entry, testsuite)?;
|
write_testsuite_tests(out, dir_entry, testsuite)?;
|
||||||
}
|
}
|
||||||
@@ -78,8 +81,12 @@ fn write_testsuite_tests(out: &mut File, dir_entry: DirEntry, testsuite: &str) -
|
|||||||
" fn {}() {{",
|
" fn {}() {{",
|
||||||
avoid_keywords(&stemstr.replace("-", "_"))
|
avoid_keywords(&stemstr.replace("-", "_"))
|
||||||
)?;
|
)?;
|
||||||
writeln!(out, " let mut wast_context = WastContext::new();")?;
|
|
||||||
writeln!(out, " let isa = native_isa();")?;
|
writeln!(out, " let isa = native_isa();")?;
|
||||||
|
writeln!(out, " let compiler = Compiler::new(isa);")?;
|
||||||
|
writeln!(
|
||||||
|
out,
|
||||||
|
" let mut wast_context = WastContext::new(Box::new(compiler));"
|
||||||
|
)?;
|
||||||
writeln!(out, " wast_context")?;
|
writeln!(out, " wast_context")?;
|
||||||
writeln!(out, " .register_spectest()")?;
|
writeln!(out, " .register_spectest()")?;
|
||||||
writeln!(
|
writeln!(
|
||||||
@@ -87,7 +94,7 @@ fn write_testsuite_tests(out: &mut File, dir_entry: DirEntry, testsuite: &str) -
|
|||||||
" .expect(\"instantiating \\\"spectest\\\"\");"
|
" .expect(\"instantiating \\\"spectest\\\"\");"
|
||||||
)?;
|
)?;
|
||||||
writeln!(out, " wast_context")?;
|
writeln!(out, " wast_context")?;
|
||||||
write!(out, " .run_file(&*isa, Path::new(\"")?;
|
write!(out, " .run_file(Path::new(\"")?;
|
||||||
// Write out the string with escape_debug to prevent special characters such
|
// Write out the string with escape_debug to prevent special characters such
|
||||||
// as backslash from being reinterpreted.
|
// as backslash from being reinterpreted.
|
||||||
for c in path.display().to_string().chars() {
|
for c in path.display().to_string().chars() {
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ cargo-fuzz = true
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
wasmtime-environ = { path = "../lib/environ" }
|
wasmtime-environ = { path = "../lib/environ" }
|
||||||
wasmtime-execute = { path = "../lib/execute" }
|
wasmtime-jit = { path = "../lib/jit" }
|
||||||
cranelift-codegen = "0.26.0"
|
cranelift-codegen = "0.26.0"
|
||||||
cranelift-wasm = "0.26.0"
|
cranelift-wasm = "0.26.0"
|
||||||
cranelift-native = "0.26.0"
|
cranelift-native = "0.26.0"
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ extern crate cranelift_codegen;
|
|||||||
extern crate cranelift_native;
|
extern crate cranelift_native;
|
||||||
extern crate wasmparser;
|
extern crate wasmparser;
|
||||||
extern crate wasmtime_environ;
|
extern crate wasmtime_environ;
|
||||||
extern crate wasmtime_execute;
|
extern crate wasmtime_jit;
|
||||||
|
|
||||||
use cranelift_codegen::settings;
|
use cranelift_codegen::settings;
|
||||||
use wasmparser::validate;
|
use wasmparser::validate;
|
||||||
@@ -28,9 +28,8 @@ fuzz_target!(|data: &[u8]| {
|
|||||||
Err(_) => return,
|
Err(_) => return,
|
||||||
};
|
};
|
||||||
let imports_resolver = |_env: &str, _function: &str| None;
|
let imports_resolver = |_env: &str, _function: &str| None;
|
||||||
let _exec =
|
let _exec = match wasmtime_jit::compile_and_link_module(&*isa, &translation, imports_resolver) {
|
||||||
match wasmtime_execute::compile_and_link_module(&*isa, &translation, imports_resolver) {
|
Ok(x) => x,
|
||||||
Ok(x) => x,
|
Err(_) => return,
|
||||||
Err(_) => return,
|
};
|
||||||
};
|
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
This is the `wasmtime-environ` crate, which contains the implementations
|
This is the `wasmtime-environ` crate, which contains the implementations
|
||||||
of the `ModuleEnvironment` and `FuncEnvironment` traits from
|
of the `ModuleEnvironment` and `FuncEnvironment` traits from
|
||||||
[`cranelift-wasm`](https://crates.io/crates/cranelift-wasm). They effectively
|
[`cranelift-wasm`](https://crates.io/crates/cranelift-wasm). They effectively
|
||||||
implement an ABI for basic wasm compilation, which can be used for JITing,
|
implement an ABI for basic wasm compilation that defines how linear memories
|
||||||
native object files, or other purposes.
|
are allocated, how indirect calls work, and other details. They can be used
|
||||||
|
for JITing, native object files, or other purposes.
|
||||||
|
|||||||
@@ -3,16 +3,9 @@
|
|||||||
|
|
||||||
use cranelift_codegen::binemit;
|
use cranelift_codegen::binemit;
|
||||||
use cranelift_codegen::ir;
|
use cranelift_codegen::ir;
|
||||||
use cranelift_codegen::ir::ExternalName;
|
use cranelift_codegen::CodegenError;
|
||||||
use cranelift_codegen::isa;
|
|
||||||
use cranelift_codegen::{CodegenError, Context};
|
|
||||||
use cranelift_entity::PrimaryMap;
|
use cranelift_entity::PrimaryMap;
|
||||||
use cranelift_wasm::{DefinedFuncIndex, FuncIndex, FuncTranslator, WasmError};
|
use cranelift_wasm::{DefinedFuncIndex, FuncIndex, WasmError};
|
||||||
use func_environ::{
|
|
||||||
get_func_name, get_imported_memory32_grow_name, get_imported_memory32_size_name,
|
|
||||||
get_memory32_grow_name, get_memory32_size_name, FuncEnvironment,
|
|
||||||
};
|
|
||||||
use module::Module;
|
|
||||||
use std::vec::Vec;
|
use std::vec::Vec;
|
||||||
|
|
||||||
/// The result of compiling a WebAssemby module's functions.
|
/// The result of compiling a WebAssemby module's functions.
|
||||||
@@ -29,71 +22,6 @@ impl Compilation {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Implementation of a relocation sink that just saves all the information for later
|
|
||||||
pub struct RelocSink {
|
|
||||||
/// Relocations recorded for the function.
|
|
||||||
pub func_relocs: Vec<Relocation>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl binemit::RelocSink for RelocSink {
|
|
||||||
fn reloc_ebb(
|
|
||||||
&mut self,
|
|
||||||
_offset: binemit::CodeOffset,
|
|
||||||
_reloc: binemit::Reloc,
|
|
||||||
_ebb_offset: binemit::CodeOffset,
|
|
||||||
) {
|
|
||||||
// This should use the `offsets` field of `ir::Function`.
|
|
||||||
panic!("ebb headers not yet implemented");
|
|
||||||
}
|
|
||||||
fn reloc_external(
|
|
||||||
&mut self,
|
|
||||||
offset: binemit::CodeOffset,
|
|
||||||
reloc: binemit::Reloc,
|
|
||||||
name: &ExternalName,
|
|
||||||
addend: binemit::Addend,
|
|
||||||
) {
|
|
||||||
let reloc_target = if *name == get_memory32_grow_name() {
|
|
||||||
RelocationTarget::Memory32Grow
|
|
||||||
} else if *name == get_imported_memory32_grow_name() {
|
|
||||||
RelocationTarget::ImportedMemory32Grow
|
|
||||||
} else if *name == get_memory32_size_name() {
|
|
||||||
RelocationTarget::Memory32Size
|
|
||||||
} else if *name == get_imported_memory32_size_name() {
|
|
||||||
RelocationTarget::ImportedMemory32Size
|
|
||||||
} else if let ExternalName::User { namespace, index } = *name {
|
|
||||||
debug_assert!(namespace == 0);
|
|
||||||
RelocationTarget::UserFunc(FuncIndex::from_u32(index))
|
|
||||||
} else if let ExternalName::LibCall(libcall) = *name {
|
|
||||||
RelocationTarget::LibCall(libcall)
|
|
||||||
} else {
|
|
||||||
panic!("unrecognized external name")
|
|
||||||
};
|
|
||||||
self.func_relocs.push(Relocation {
|
|
||||||
reloc,
|
|
||||||
reloc_target,
|
|
||||||
offset,
|
|
||||||
addend,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
fn reloc_jt(
|
|
||||||
&mut self,
|
|
||||||
_offset: binemit::CodeOffset,
|
|
||||||
_reloc: binemit::Reloc,
|
|
||||||
_jt: ir::JumpTable,
|
|
||||||
) {
|
|
||||||
panic!("jump tables not yet implemented");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl RelocSink {
|
|
||||||
/// Return a new `RelocSink` instance.
|
|
||||||
pub fn new() -> Self {
|
|
||||||
Self {
|
|
||||||
func_relocs: Vec::new(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A record of a relocation to perform.
|
/// A record of a relocation to perform.
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct Relocation {
|
pub struct Relocation {
|
||||||
@@ -127,44 +55,6 @@ pub enum RelocationTarget {
|
|||||||
/// Relocations to apply to function bodies.
|
/// Relocations to apply to function bodies.
|
||||||
pub type Relocations = PrimaryMap<DefinedFuncIndex, Vec<Relocation>>;
|
pub type Relocations = PrimaryMap<DefinedFuncIndex, Vec<Relocation>>;
|
||||||
|
|
||||||
/// Compile the module, producing a compilation result with associated
|
|
||||||
/// relocations.
|
|
||||||
pub fn compile_module<'data, 'module>(
|
|
||||||
module: &'module Module,
|
|
||||||
function_body_inputs: &PrimaryMap<DefinedFuncIndex, &'data [u8]>,
|
|
||||||
isa: &isa::TargetIsa,
|
|
||||||
) -> Result<(Compilation, Relocations), CompileError> {
|
|
||||||
let mut functions = PrimaryMap::new();
|
|
||||||
let mut relocations = PrimaryMap::new();
|
|
||||||
for (i, input) in function_body_inputs.iter() {
|
|
||||||
let func_index = module.func_index(i);
|
|
||||||
let mut context = Context::new();
|
|
||||||
context.func.name = get_func_name(func_index);
|
|
||||||
context.func.signature = module.signatures[module.functions[func_index]].clone();
|
|
||||||
|
|
||||||
let mut trans = FuncTranslator::new();
|
|
||||||
trans
|
|
||||||
.translate(
|
|
||||||
input,
|
|
||||||
&mut context.func,
|
|
||||||
&mut FuncEnvironment::new(isa, module),
|
|
||||||
)
|
|
||||||
.map_err(CompileError::Wasm)?;
|
|
||||||
|
|
||||||
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(CompileError::Codegen)?;
|
|
||||||
functions.push(code_buf);
|
|
||||||
relocations.push(reloc_sink.func_relocs);
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: Reorganize where we create the Vec for the resolved imports.
|
|
||||||
Ok((Compilation::new(functions), relocations))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// An error while compiling WebAssembly to machine code.
|
/// An error while compiling WebAssembly to machine code.
|
||||||
#[derive(Fail, Debug)]
|
#[derive(Fail, Debug)]
|
||||||
pub enum CompileError {
|
pub enum CompileError {
|
||||||
|
|||||||
119
lib/environ/src/cranelift.rs
Normal file
119
lib/environ/src/cranelift.rs
Normal file
@@ -0,0 +1,119 @@
|
|||||||
|
//! Support for compiling with Cranelift.
|
||||||
|
|
||||||
|
use compilation::{Compilation, CompileError, Relocation, RelocationTarget, Relocations};
|
||||||
|
use cranelift_codegen::binemit;
|
||||||
|
use cranelift_codegen::ir;
|
||||||
|
use cranelift_codegen::ir::ExternalName;
|
||||||
|
use cranelift_codegen::isa;
|
||||||
|
use cranelift_codegen::Context;
|
||||||
|
use cranelift_entity::PrimaryMap;
|
||||||
|
use cranelift_wasm::{DefinedFuncIndex, FuncIndex, FuncTranslator};
|
||||||
|
use func_environ::{
|
||||||
|
get_func_name, get_imported_memory32_grow_name, get_imported_memory32_size_name,
|
||||||
|
get_memory32_grow_name, get_memory32_size_name, FuncEnvironment,
|
||||||
|
};
|
||||||
|
use module::Module;
|
||||||
|
use std::vec::Vec;
|
||||||
|
|
||||||
|
/// Implementation of a relocation sink that just saves all the information for later
|
||||||
|
struct RelocSink {
|
||||||
|
/// Relocations recorded for the function.
|
||||||
|
func_relocs: Vec<Relocation>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl binemit::RelocSink for RelocSink {
|
||||||
|
fn reloc_ebb(
|
||||||
|
&mut self,
|
||||||
|
_offset: binemit::CodeOffset,
|
||||||
|
_reloc: binemit::Reloc,
|
||||||
|
_ebb_offset: binemit::CodeOffset,
|
||||||
|
) {
|
||||||
|
// This should use the `offsets` field of `ir::Function`.
|
||||||
|
panic!("ebb headers not yet implemented");
|
||||||
|
}
|
||||||
|
fn reloc_external(
|
||||||
|
&mut self,
|
||||||
|
offset: binemit::CodeOffset,
|
||||||
|
reloc: binemit::Reloc,
|
||||||
|
name: &ExternalName,
|
||||||
|
addend: binemit::Addend,
|
||||||
|
) {
|
||||||
|
let reloc_target = if *name == get_memory32_grow_name() {
|
||||||
|
RelocationTarget::Memory32Grow
|
||||||
|
} else if *name == get_imported_memory32_grow_name() {
|
||||||
|
RelocationTarget::ImportedMemory32Grow
|
||||||
|
} else if *name == get_memory32_size_name() {
|
||||||
|
RelocationTarget::Memory32Size
|
||||||
|
} else if *name == get_imported_memory32_size_name() {
|
||||||
|
RelocationTarget::ImportedMemory32Size
|
||||||
|
} else if let ExternalName::User { namespace, index } = *name {
|
||||||
|
debug_assert!(namespace == 0);
|
||||||
|
RelocationTarget::UserFunc(FuncIndex::from_u32(index))
|
||||||
|
} else if let ExternalName::LibCall(libcall) = *name {
|
||||||
|
RelocationTarget::LibCall(libcall)
|
||||||
|
} else {
|
||||||
|
panic!("unrecognized external name")
|
||||||
|
};
|
||||||
|
self.func_relocs.push(Relocation {
|
||||||
|
reloc,
|
||||||
|
reloc_target,
|
||||||
|
offset,
|
||||||
|
addend,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
fn reloc_jt(
|
||||||
|
&mut self,
|
||||||
|
_offset: binemit::CodeOffset,
|
||||||
|
_reloc: binemit::Reloc,
|
||||||
|
_jt: ir::JumpTable,
|
||||||
|
) {
|
||||||
|
panic!("jump tables not yet implemented");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RelocSink {
|
||||||
|
/// Return a new `RelocSink` instance.
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
func_relocs: Vec::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Compile the module using Cranelift, producing a compilation result with
|
||||||
|
/// associated relocations.
|
||||||
|
pub fn compile_module<'data, 'module>(
|
||||||
|
module: &'module Module,
|
||||||
|
function_body_inputs: PrimaryMap<DefinedFuncIndex, &'data [u8]>,
|
||||||
|
isa: &isa::TargetIsa,
|
||||||
|
) -> Result<(Compilation, Relocations), CompileError> {
|
||||||
|
let mut functions = PrimaryMap::new();
|
||||||
|
let mut relocations = PrimaryMap::new();
|
||||||
|
for (i, input) in function_body_inputs.into_iter() {
|
||||||
|
let func_index = module.func_index(i);
|
||||||
|
let mut context = Context::new();
|
||||||
|
context.func.name = get_func_name(func_index);
|
||||||
|
context.func.signature = module.signatures[module.functions[func_index]].clone();
|
||||||
|
|
||||||
|
let mut trans = FuncTranslator::new();
|
||||||
|
trans
|
||||||
|
.translate(
|
||||||
|
input,
|
||||||
|
&mut context.func,
|
||||||
|
&mut FuncEnvironment::new(isa.frontend_config(), module),
|
||||||
|
)
|
||||||
|
.map_err(CompileError::Wasm)?;
|
||||||
|
|
||||||
|
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(CompileError::Codegen)?;
|
||||||
|
functions.push(code_buf);
|
||||||
|
relocations.push(reloc_sink.func_relocs);
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Reorganize where we create the Vec for the resolved imports.
|
||||||
|
Ok((Compilation::new(functions), relocations))
|
||||||
|
}
|
||||||
@@ -7,7 +7,7 @@ use cranelift_codegen::ir::types::*;
|
|||||||
use cranelift_codegen::ir::{
|
use cranelift_codegen::ir::{
|
||||||
AbiParam, ArgumentPurpose, ExtFuncData, FuncRef, Function, InstBuilder, Signature,
|
AbiParam, ArgumentPurpose, ExtFuncData, FuncRef, Function, InstBuilder, Signature,
|
||||||
};
|
};
|
||||||
use cranelift_codegen::isa;
|
use cranelift_codegen::isa::TargetFrontendConfig;
|
||||||
use cranelift_entity::EntityRef;
|
use cranelift_entity::EntityRef;
|
||||||
use cranelift_wasm::{
|
use cranelift_wasm::{
|
||||||
self, DefinedGlobalIndex, DefinedMemoryIndex, DefinedTableIndex, FuncIndex, GlobalIndex,
|
self, DefinedGlobalIndex, DefinedMemoryIndex, DefinedTableIndex, FuncIndex, GlobalIndex,
|
||||||
@@ -50,8 +50,8 @@ pub fn get_imported_memory32_size_name() -> ir::ExternalName {
|
|||||||
|
|
||||||
/// The FuncEnvironment implementation for use by the `ModuleEnvironment`.
|
/// The FuncEnvironment implementation for use by the `ModuleEnvironment`.
|
||||||
pub struct FuncEnvironment<'module_environment> {
|
pub struct FuncEnvironment<'module_environment> {
|
||||||
/// Compilation setting flags.
|
/// Target-specified configuration.
|
||||||
isa: &'module_environment isa::TargetIsa,
|
target_config: TargetFrontendConfig,
|
||||||
|
|
||||||
/// The module-level environment which this function-level environment belongs to.
|
/// The module-level environment which this function-level environment belongs to.
|
||||||
module: &'module_environment Module,
|
module: &'module_environment Module,
|
||||||
@@ -104,12 +104,9 @@ pub struct FuncEnvironment<'module_environment> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl<'module_environment> FuncEnvironment<'module_environment> {
|
impl<'module_environment> FuncEnvironment<'module_environment> {
|
||||||
pub fn new(
|
pub fn new(target_config: TargetFrontendConfig, module: &'module_environment Module) -> Self {
|
||||||
isa: &'module_environment isa::TargetIsa,
|
|
||||||
module: &'module_environment Module,
|
|
||||||
) -> Self {
|
|
||||||
Self {
|
Self {
|
||||||
isa,
|
target_config,
|
||||||
module,
|
module,
|
||||||
vmctx: None,
|
vmctx: None,
|
||||||
imported_functions_base: None,
|
imported_functions_base: None,
|
||||||
@@ -124,12 +121,12 @@ impl<'module_environment> FuncEnvironment<'module_environment> {
|
|||||||
imported_memory32_size_extfunc: None,
|
imported_memory32_size_extfunc: None,
|
||||||
memory_grow_extfunc: None,
|
memory_grow_extfunc: None,
|
||||||
imported_memory_grow_extfunc: None,
|
imported_memory_grow_extfunc: None,
|
||||||
offsets: VMOffsets::new(isa.pointer_bytes()),
|
offsets: VMOffsets::new(target_config.pointer_bytes()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn pointer_type(&self) -> ir::Type {
|
fn pointer_type(&self) -> ir::Type {
|
||||||
self.isa.frontend_config().pointer_type()
|
self.target_config.pointer_type()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn vmctx(&mut self, func: &mut Function) -> ir::GlobalValue {
|
fn vmctx(&mut self, func: &mut Function) -> ir::GlobalValue {
|
||||||
@@ -253,7 +250,7 @@ impl<'module_environment> FuncEnvironment<'module_environment> {
|
|||||||
AbiParam::special(self.pointer_type(), ArgumentPurpose::VMContext),
|
AbiParam::special(self.pointer_type(), ArgumentPurpose::VMContext),
|
||||||
],
|
],
|
||||||
returns: vec![AbiParam::new(I32)],
|
returns: vec![AbiParam::new(I32)],
|
||||||
call_conv: self.isa.frontend_config().default_call_conv,
|
call_conv: self.target_config.default_call_conv,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -303,7 +300,7 @@ impl<'module_environment> FuncEnvironment<'module_environment> {
|
|||||||
AbiParam::special(self.pointer_type(), ArgumentPurpose::VMContext),
|
AbiParam::special(self.pointer_type(), ArgumentPurpose::VMContext),
|
||||||
],
|
],
|
||||||
returns: vec![AbiParam::new(I32)],
|
returns: vec![AbiParam::new(I32)],
|
||||||
call_conv: self.isa.frontend_config().default_call_conv,
|
call_conv: self.target_config.default_call_conv,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -348,8 +345,8 @@ impl<'module_environment> FuncEnvironment<'module_environment> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl<'module_environment> cranelift_wasm::FuncEnvironment for FuncEnvironment<'module_environment> {
|
impl<'module_environment> cranelift_wasm::FuncEnvironment for FuncEnvironment<'module_environment> {
|
||||||
fn target_config(&self) -> isa::TargetFrontendConfig {
|
fn target_config(&self) -> TargetFrontendConfig {
|
||||||
self.isa.frontend_config()
|
self.target_config
|
||||||
}
|
}
|
||||||
|
|
||||||
fn make_table(&mut self, func: &mut ir::Function, index: TableIndex) -> ir::Table {
|
fn make_table(&mut self, func: &mut ir::Function, index: TableIndex) -> ir::Table {
|
||||||
|
|||||||
@@ -45,12 +45,13 @@ mod module_environ;
|
|||||||
mod tunables;
|
mod tunables;
|
||||||
mod vmoffsets;
|
mod vmoffsets;
|
||||||
|
|
||||||
pub use compilation::{
|
pub mod cranelift;
|
||||||
compile_module, Compilation, CompileError, RelocSink, Relocation, RelocationTarget, Relocations,
|
|
||||||
};
|
pub use compilation::{Compilation, CompileError, Relocation, RelocationTarget, Relocations};
|
||||||
pub use module::{Export, MemoryPlan, MemoryStyle, Module, TableElements, TablePlan, TableStyle};
|
pub use module::{Export, MemoryPlan, MemoryStyle, Module, TableElements, TablePlan, TableStyle};
|
||||||
pub use module_environ::{
|
pub use module_environ::{
|
||||||
translate_signature, DataInitializer, ModuleEnvironment, ModuleTranslation,
|
translate_signature, DataInitializer, DataInitializerLocation, ModuleEnvironment,
|
||||||
|
ModuleTranslation,
|
||||||
};
|
};
|
||||||
pub use tunables::Tunables;
|
pub use tunables::Tunables;
|
||||||
pub use vmoffsets::VMOffsets;
|
pub use vmoffsets::VMOffsets;
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ use cranelift_wasm::{
|
|||||||
DefinedFuncIndex, DefinedGlobalIndex, DefinedMemoryIndex, DefinedTableIndex, FuncIndex, Global,
|
DefinedFuncIndex, DefinedGlobalIndex, DefinedMemoryIndex, DefinedTableIndex, FuncIndex, Global,
|
||||||
GlobalIndex, Memory, MemoryIndex, SignatureIndex, Table, TableIndex,
|
GlobalIndex, Memory, MemoryIndex, SignatureIndex, Table, TableIndex,
|
||||||
};
|
};
|
||||||
use std::cmp;
|
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::string::String;
|
use std::string::String;
|
||||||
use std::vec::Vec;
|
use std::vec::Vec;
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
use cranelift_codegen::ir;
|
use cranelift_codegen::ir;
|
||||||
use cranelift_codegen::ir::{AbiParam, ArgumentPurpose};
|
use cranelift_codegen::ir::{AbiParam, ArgumentPurpose};
|
||||||
use cranelift_codegen::isa;
|
use cranelift_codegen::isa::TargetFrontendConfig;
|
||||||
use cranelift_entity::PrimaryMap;
|
use cranelift_entity::PrimaryMap;
|
||||||
use cranelift_wasm::{
|
use cranelift_wasm::{
|
||||||
self, translate_module, DefinedFuncIndex, FuncIndex, Global, GlobalIndex, Memory, MemoryIndex,
|
self, translate_module, DefinedFuncIndex, FuncIndex, Global, GlobalIndex, Memory, MemoryIndex,
|
||||||
@@ -13,139 +13,149 @@ use std::string::String;
|
|||||||
use std::vec::Vec;
|
use std::vec::Vec;
|
||||||
use tunables::Tunables;
|
use tunables::Tunables;
|
||||||
|
|
||||||
/// Object containing the standalone environment information. To be passed after creation as
|
/// The result of translating via `ModuleEnvironment`. Function bodies are not
|
||||||
/// argument to `compile_module`.
|
/// yet translated, and data initializers have not yet been copied out of the
|
||||||
pub struct ModuleEnvironment<'data, 'module> {
|
/// original buffer.
|
||||||
|
pub struct ModuleTranslation<'data> {
|
||||||
/// Compilation setting flags.
|
/// Compilation setting flags.
|
||||||
isa: &'module isa::TargetIsa,
|
pub target_config: TargetFrontendConfig,
|
||||||
|
|
||||||
/// Module information.
|
/// Module information.
|
||||||
module: &'module mut Module,
|
pub module: Module,
|
||||||
|
|
||||||
/// References to information to be decoded later.
|
/// References to the function bodies.
|
||||||
lazy: LazyContents<'data>,
|
pub function_body_inputs: PrimaryMap<DefinedFuncIndex, &'data [u8]>,
|
||||||
|
|
||||||
|
/// References to the data initializers.
|
||||||
|
pub data_initializers: Vec<DataInitializer<'data>>,
|
||||||
|
|
||||||
/// Tunable parameters.
|
/// Tunable parameters.
|
||||||
tunables: Tunables,
|
pub tunables: Tunables,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'data, 'module> ModuleEnvironment<'data, 'module> {
|
impl<'data> ModuleTranslation<'data> {
|
||||||
/// Allocates the enironment data structures with the given isa.
|
/// Return a new `FuncEnvironment` for translating a function.
|
||||||
pub fn new(
|
pub fn func_env(&self) -> FuncEnvironment {
|
||||||
isa: &'module isa::TargetIsa,
|
FuncEnvironment::new(self.target_config, &self.module)
|
||||||
module: &'module mut Module,
|
}
|
||||||
tunables: Tunables,
|
}
|
||||||
) -> Self {
|
|
||||||
|
/// Object containing the standalone environment information.
|
||||||
|
pub struct ModuleEnvironment<'data> {
|
||||||
|
/// The result to be filled in.
|
||||||
|
result: ModuleTranslation<'data>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'data> ModuleEnvironment<'data> {
|
||||||
|
/// Allocates the enironment data structures.
|
||||||
|
pub fn new(target_config: TargetFrontendConfig, tunables: Tunables) -> Self {
|
||||||
Self {
|
Self {
|
||||||
isa,
|
result: ModuleTranslation {
|
||||||
module,
|
target_config,
|
||||||
lazy: LazyContents::new(),
|
module: Module::new(),
|
||||||
tunables,
|
function_body_inputs: PrimaryMap::new(),
|
||||||
|
data_initializers: Vec::new(),
|
||||||
|
tunables,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn pointer_type(&self) -> ir::Type {
|
fn pointer_type(&self) -> ir::Type {
|
||||||
self.isa.frontend_config().pointer_type()
|
self.result.target_config.pointer_type()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Translate the given wasm module data using this environment. This consumes the
|
/// Translate a wasm module using this environment. This consumes the
|
||||||
/// `ModuleEnvironment` with its mutable reference to the `Module` and produces a
|
/// `ModuleEnvironment` and produces a `ModuleTranslation`.
|
||||||
/// `ModuleTranslation` with an immutable reference to the `Module` (which has
|
pub fn translate(mut self, data: &'data [u8]) -> WasmResult<ModuleTranslation<'data>> {
|
||||||
/// become fully populated).
|
|
||||||
pub fn translate(mut self, data: &'data [u8]) -> WasmResult<ModuleTranslation<'data, 'module>> {
|
|
||||||
translate_module(data, &mut self)?;
|
translate_module(data, &mut self)?;
|
||||||
|
|
||||||
Ok(ModuleTranslation {
|
Ok(self.result)
|
||||||
isa: self.isa,
|
|
||||||
module: self.module,
|
|
||||||
lazy: self.lazy,
|
|
||||||
tunables: self.tunables,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// This trait is useful for `translate_module` because it tells how to translate
|
/// This trait is useful for `translate_module` because it tells how to translate
|
||||||
/// enironment-dependent wasm instructions. These functions should not be called by the user.
|
/// enironment-dependent wasm instructions. These functions should not be called by the user.
|
||||||
impl<'data, 'module> cranelift_wasm::ModuleEnvironment<'data>
|
impl<'data> cranelift_wasm::ModuleEnvironment<'data> for ModuleEnvironment<'data> {
|
||||||
for ModuleEnvironment<'data, 'module>
|
fn target_config(&self) -> TargetFrontendConfig {
|
||||||
{
|
self.result.target_config
|
||||||
fn target_config(&self) -> isa::TargetFrontendConfig {
|
|
||||||
self.isa.frontend_config()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn declare_signature(&mut self, sig: &ir::Signature) {
|
fn declare_signature(&mut self, sig: &ir::Signature) {
|
||||||
let sig = translate_signature(sig.clone(), self.pointer_type());
|
let sig = translate_signature(sig.clone(), self.pointer_type());
|
||||||
// TODO: Deduplicate signatures.
|
// TODO: Deduplicate signatures.
|
||||||
self.module.signatures.push(sig);
|
self.result.module.signatures.push(sig);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_signature(&self, sig_index: SignatureIndex) -> &ir::Signature {
|
fn get_signature(&self, sig_index: SignatureIndex) -> &ir::Signature {
|
||||||
&self.module.signatures[sig_index]
|
&self.result.module.signatures[sig_index]
|
||||||
}
|
}
|
||||||
|
|
||||||
fn declare_func_import(&mut self, sig_index: SignatureIndex, module: &str, field: &str) {
|
fn declare_func_import(&mut self, sig_index: SignatureIndex, module: &str, field: &str) {
|
||||||
debug_assert_eq!(
|
debug_assert_eq!(
|
||||||
self.module.functions.len(),
|
self.result.module.functions.len(),
|
||||||
self.module.imported_funcs.len(),
|
self.result.module.imported_funcs.len(),
|
||||||
"Imported functions must be declared first"
|
"Imported functions must be declared first"
|
||||||
);
|
);
|
||||||
self.module.functions.push(sig_index);
|
self.result.module.functions.push(sig_index);
|
||||||
|
|
||||||
self.module
|
self.result
|
||||||
|
.module
|
||||||
.imported_funcs
|
.imported_funcs
|
||||||
.push((String::from(module), String::from(field)));
|
.push((String::from(module), String::from(field)));
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_num_func_imports(&self) -> usize {
|
fn get_num_func_imports(&self) -> usize {
|
||||||
self.module.imported_funcs.len()
|
self.result.module.imported_funcs.len()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn declare_func_type(&mut self, sig_index: SignatureIndex) {
|
fn declare_func_type(&mut self, sig_index: SignatureIndex) {
|
||||||
self.module.functions.push(sig_index);
|
self.result.module.functions.push(sig_index);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_func_type(&self, func_index: FuncIndex) -> SignatureIndex {
|
fn get_func_type(&self, func_index: FuncIndex) -> SignatureIndex {
|
||||||
self.module.functions[func_index]
|
self.result.module.functions[func_index]
|
||||||
}
|
}
|
||||||
|
|
||||||
fn declare_global_import(&mut self, global: Global, module: &str, field: &str) {
|
fn declare_global_import(&mut self, global: Global, module: &str, field: &str) {
|
||||||
debug_assert_eq!(
|
debug_assert_eq!(
|
||||||
self.module.globals.len(),
|
self.result.module.globals.len(),
|
||||||
self.module.imported_globals.len(),
|
self.result.module.imported_globals.len(),
|
||||||
"Imported globals must be declared first"
|
"Imported globals must be declared first"
|
||||||
);
|
);
|
||||||
self.module.globals.push(global);
|
self.result.module.globals.push(global);
|
||||||
|
|
||||||
self.module
|
self.result
|
||||||
|
.module
|
||||||
.imported_globals
|
.imported_globals
|
||||||
.push((String::from(module), String::from(field)));
|
.push((String::from(module), String::from(field)));
|
||||||
}
|
}
|
||||||
|
|
||||||
fn declare_global(&mut self, global: Global) {
|
fn declare_global(&mut self, global: Global) {
|
||||||
self.module.globals.push(global);
|
self.result.module.globals.push(global);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_global(&self, global_index: GlobalIndex) -> &Global {
|
fn get_global(&self, global_index: GlobalIndex) -> &Global {
|
||||||
&self.module.globals[global_index]
|
&self.result.module.globals[global_index]
|
||||||
}
|
}
|
||||||
|
|
||||||
fn declare_table_import(&mut self, table: Table, module: &str, field: &str) {
|
fn declare_table_import(&mut self, table: Table, module: &str, field: &str) {
|
||||||
debug_assert_eq!(
|
debug_assert_eq!(
|
||||||
self.module.table_plans.len(),
|
self.result.module.table_plans.len(),
|
||||||
self.module.imported_tables.len(),
|
self.result.module.imported_tables.len(),
|
||||||
"Imported tables must be declared first"
|
"Imported tables must be declared first"
|
||||||
);
|
);
|
||||||
let plan = TablePlan::for_table(table, &self.tunables);
|
let plan = TablePlan::for_table(table, &self.result.tunables);
|
||||||
self.module.table_plans.push(plan);
|
self.result.module.table_plans.push(plan);
|
||||||
|
|
||||||
self.module
|
self.result
|
||||||
|
.module
|
||||||
.imported_tables
|
.imported_tables
|
||||||
.push((String::from(module), String::from(field)));
|
.push((String::from(module), String::from(field)));
|
||||||
}
|
}
|
||||||
|
|
||||||
fn declare_table(&mut self, table: Table) {
|
fn declare_table(&mut self, table: Table) {
|
||||||
let plan = TablePlan::for_table(table, &self.tunables);
|
let plan = TablePlan::for_table(table, &self.result.tunables);
|
||||||
self.module.table_plans.push(plan);
|
self.result.module.table_plans.push(plan);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn declare_table_elements(
|
fn declare_table_elements(
|
||||||
@@ -155,7 +165,7 @@ impl<'data, 'module> cranelift_wasm::ModuleEnvironment<'data>
|
|||||||
offset: usize,
|
offset: usize,
|
||||||
elements: Vec<FuncIndex>,
|
elements: Vec<FuncIndex>,
|
||||||
) {
|
) {
|
||||||
self.module.table_elements.push(TableElements {
|
self.result.module.table_elements.push(TableElements {
|
||||||
table_index,
|
table_index,
|
||||||
base,
|
base,
|
||||||
offset,
|
offset,
|
||||||
@@ -165,21 +175,22 @@ impl<'data, 'module> cranelift_wasm::ModuleEnvironment<'data>
|
|||||||
|
|
||||||
fn declare_memory_import(&mut self, memory: Memory, module: &str, field: &str) {
|
fn declare_memory_import(&mut self, memory: Memory, module: &str, field: &str) {
|
||||||
debug_assert_eq!(
|
debug_assert_eq!(
|
||||||
self.module.memory_plans.len(),
|
self.result.module.memory_plans.len(),
|
||||||
self.module.imported_memories.len(),
|
self.result.module.imported_memories.len(),
|
||||||
"Imported memories must be declared first"
|
"Imported memories must be declared first"
|
||||||
);
|
);
|
||||||
let plan = MemoryPlan::for_memory(memory, &self.tunables);
|
let plan = MemoryPlan::for_memory(memory, &self.result.tunables);
|
||||||
self.module.memory_plans.push(plan);
|
self.result.module.memory_plans.push(plan);
|
||||||
|
|
||||||
self.module
|
self.result
|
||||||
|
.module
|
||||||
.imported_memories
|
.imported_memories
|
||||||
.push((String::from(module), String::from(field)));
|
.push((String::from(module), String::from(field)));
|
||||||
}
|
}
|
||||||
|
|
||||||
fn declare_memory(&mut self, memory: Memory) {
|
fn declare_memory(&mut self, memory: Memory) {
|
||||||
let plan = MemoryPlan::for_memory(memory, &self.tunables);
|
let plan = MemoryPlan::for_memory(memory, &self.result.tunables);
|
||||||
self.module.memory_plans.push(plan);
|
self.result.module.memory_plans.push(plan);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn declare_data_initialization(
|
fn declare_data_initialization(
|
||||||
@@ -189,71 +200,55 @@ impl<'data, 'module> cranelift_wasm::ModuleEnvironment<'data>
|
|||||||
offset: usize,
|
offset: usize,
|
||||||
data: &'data [u8],
|
data: &'data [u8],
|
||||||
) {
|
) {
|
||||||
self.lazy.data_initializers.push(DataInitializer {
|
self.result.data_initializers.push(DataInitializer {
|
||||||
memory_index,
|
location: DataInitializerLocation {
|
||||||
base,
|
memory_index,
|
||||||
offset,
|
base,
|
||||||
|
offset,
|
||||||
|
},
|
||||||
data,
|
data,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
fn declare_func_export(&mut self, func_index: FuncIndex, name: &str) {
|
fn declare_func_export(&mut self, func_index: FuncIndex, name: &str) {
|
||||||
self.module
|
self.result
|
||||||
|
.module
|
||||||
.exports
|
.exports
|
||||||
.insert(String::from(name), Export::Function(func_index));
|
.insert(String::from(name), Export::Function(func_index));
|
||||||
}
|
}
|
||||||
|
|
||||||
fn declare_table_export(&mut self, table_index: TableIndex, name: &str) {
|
fn declare_table_export(&mut self, table_index: TableIndex, name: &str) {
|
||||||
self.module
|
self.result
|
||||||
|
.module
|
||||||
.exports
|
.exports
|
||||||
.insert(String::from(name), Export::Table(table_index));
|
.insert(String::from(name), Export::Table(table_index));
|
||||||
}
|
}
|
||||||
|
|
||||||
fn declare_memory_export(&mut self, memory_index: MemoryIndex, name: &str) {
|
fn declare_memory_export(&mut self, memory_index: MemoryIndex, name: &str) {
|
||||||
self.module
|
self.result
|
||||||
|
.module
|
||||||
.exports
|
.exports
|
||||||
.insert(String::from(name), Export::Memory(memory_index));
|
.insert(String::from(name), Export::Memory(memory_index));
|
||||||
}
|
}
|
||||||
|
|
||||||
fn declare_global_export(&mut self, global_index: GlobalIndex, name: &str) {
|
fn declare_global_export(&mut self, global_index: GlobalIndex, name: &str) {
|
||||||
self.module
|
self.result
|
||||||
|
.module
|
||||||
.exports
|
.exports
|
||||||
.insert(String::from(name), Export::Global(global_index));
|
.insert(String::from(name), Export::Global(global_index));
|
||||||
}
|
}
|
||||||
|
|
||||||
fn declare_start_func(&mut self, func_index: FuncIndex) {
|
fn declare_start_func(&mut self, func_index: FuncIndex) {
|
||||||
debug_assert!(self.module.start_func.is_none());
|
debug_assert!(self.result.module.start_func.is_none());
|
||||||
self.module.start_func = Some(func_index);
|
self.result.module.start_func = Some(func_index);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn define_function_body(&mut self, body_bytes: &'data [u8]) -> WasmResult<()> {
|
fn define_function_body(&mut self, body_bytes: &'data [u8]) -> WasmResult<()> {
|
||||||
self.lazy.function_body_inputs.push(body_bytes);
|
self.result.function_body_inputs.push(body_bytes);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The result of translating via `ModuleEnvironment`.
|
|
||||||
pub struct ModuleTranslation<'data, 'module> {
|
|
||||||
/// Compilation setting flags.
|
|
||||||
pub isa: &'module isa::TargetIsa,
|
|
||||||
|
|
||||||
/// Module information.
|
|
||||||
pub module: &'module Module,
|
|
||||||
|
|
||||||
/// Pointers into the raw data buffer.
|
|
||||||
pub lazy: LazyContents<'data>,
|
|
||||||
|
|
||||||
/// Tunable parameters.
|
|
||||||
pub tunables: Tunables,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'data, 'module> ModuleTranslation<'data, 'module> {
|
|
||||||
/// Return a new `FuncEnvironment` for translating a function.
|
|
||||||
pub fn func_env(&self) -> FuncEnvironment {
|
|
||||||
FuncEnvironment::new(self.isa, &self.module)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Add environment-specific function parameters.
|
/// Add environment-specific function parameters.
|
||||||
pub fn translate_signature(mut sig: ir::Signature, pointer_type: ir::Type) -> ir::Signature {
|
pub fn translate_signature(mut sig: ir::Signature, pointer_type: ir::Type) -> ir::Signature {
|
||||||
sig.params
|
sig.params
|
||||||
@@ -261,33 +256,25 @@ pub fn translate_signature(mut sig: ir::Signature, pointer_type: ir::Type) -> ir
|
|||||||
sig
|
sig
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A data initializer for linear memory.
|
/// A memory index and offset within that memory where a data initialization
|
||||||
pub struct DataInitializer<'data> {
|
/// should is to be performed.
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct DataInitializerLocation {
|
||||||
/// The index of the memory to initialize.
|
/// The index of the memory to initialize.
|
||||||
pub memory_index: MemoryIndex,
|
pub memory_index: MemoryIndex,
|
||||||
|
|
||||||
/// Optionally a globalvar base to initialize at.
|
/// Optionally a globalvar base to initialize at.
|
||||||
pub base: Option<GlobalIndex>,
|
pub base: Option<GlobalIndex>,
|
||||||
|
|
||||||
/// A constant offset to initialize at.
|
/// A constant offset to initialize at.
|
||||||
pub offset: usize,
|
pub offset: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A data initializer for linear memory.
|
||||||
|
pub struct DataInitializer<'data> {
|
||||||
|
/// The location where the initialization is to be performed.
|
||||||
|
pub location: DataInitializerLocation,
|
||||||
|
|
||||||
/// The initialization data.
|
/// The initialization data.
|
||||||
pub data: &'data [u8],
|
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,4 +1,4 @@
|
|||||||
//! Offsets and sizes of various structs in wasmtime-execute's vmcontext
|
//! Offsets and sizes of various structs in wasmtime-runtime's vmcontext
|
||||||
//! module.
|
//! module.
|
||||||
|
|
||||||
use cranelift_codegen::ir;
|
use cranelift_codegen::ir;
|
||||||
|
|||||||
@@ -1,4 +0,0 @@
|
|||||||
This is the `wasmtime-execute` crate, which contains wasm runtime support,
|
|
||||||
supporting the wasm ABI defined by [`wasmtime-environ`].
|
|
||||||
|
|
||||||
[`wasmtime-environ`]: https://crates.io/crates/wasmtime-environ
|
|
||||||
@@ -1,142 +0,0 @@
|
|||||||
//! Support for performing actions with a wasm module from the outside.
|
|
||||||
|
|
||||||
use cranelift_codegen::ir;
|
|
||||||
use link::LinkError;
|
|
||||||
use std::fmt;
|
|
||||||
use std::string::String;
|
|
||||||
use std::vec::Vec;
|
|
||||||
use wasmtime_environ::CompileError;
|
|
||||||
use wasmtime_runtime::InstantiationError;
|
|
||||||
|
|
||||||
/// A runtime value.
|
|
||||||
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
|
|
||||||
pub enum RuntimeValue {
|
|
||||||
/// A runtime value with type i32.
|
|
||||||
I32(i32),
|
|
||||||
/// A runtime value with type i64.
|
|
||||||
I64(i64),
|
|
||||||
/// A runtime value with type f32.
|
|
||||||
F32(u32),
|
|
||||||
/// A runtime value with type f64.
|
|
||||||
F64(u64),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl RuntimeValue {
|
|
||||||
/// Return the type of this `RuntimeValue`.
|
|
||||||
pub fn value_type(self) -> ir::Type {
|
|
||||||
match self {
|
|
||||||
RuntimeValue::I32(_) => ir::types::I32,
|
|
||||||
RuntimeValue::I64(_) => ir::types::I64,
|
|
||||||
RuntimeValue::F32(_) => ir::types::F32,
|
|
||||||
RuntimeValue::F64(_) => ir::types::F64,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Assuming this `RuntimeValue` holds an `i32`, return that value.
|
|
||||||
pub fn unwrap_i32(self) -> i32 {
|
|
||||||
match self {
|
|
||||||
RuntimeValue::I32(x) => x,
|
|
||||||
_ => panic!("unwrapping value of type {} as i32", self.value_type()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Assuming this `RuntimeValue` holds an `i64`, return that value.
|
|
||||||
pub fn unwrap_i64(self) -> i64 {
|
|
||||||
match self {
|
|
||||||
RuntimeValue::I64(x) => x,
|
|
||||||
_ => panic!("unwrapping value of type {} as i64", self.value_type()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Assuming this `RuntimeValue` holds an `f32`, return that value.
|
|
||||||
pub fn unwrap_f32(self) -> f32 {
|
|
||||||
f32::from_bits(self.unwrap_f32_bits())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Assuming this `RuntimeValue` holds an `f32`, return the bits of that value as a `u32`.
|
|
||||||
pub fn unwrap_f32_bits(self) -> u32 {
|
|
||||||
match self {
|
|
||||||
RuntimeValue::F32(x) => x,
|
|
||||||
_ => panic!("unwrapping value of type {} as f32", self.value_type()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Assuming this `RuntimeValue` holds an `f64`, return that value.
|
|
||||||
pub fn unwrap_f64(self) -> f64 {
|
|
||||||
f64::from_bits(self.unwrap_f64_bits())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Assuming this `RuntimeValue` holds an `f64`, return the bits of that value as a `u64`.
|
|
||||||
pub fn unwrap_f64_bits(self) -> u64 {
|
|
||||||
match self {
|
|
||||||
RuntimeValue::F64(x) => x,
|
|
||||||
_ => panic!("unwrapping value of type {} as f64", self.value_type()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl fmt::Display for RuntimeValue {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
||||||
match self {
|
|
||||||
RuntimeValue::I32(x) => write!(f, "{}: i32", x),
|
|
||||||
RuntimeValue::I64(x) => write!(f, "{}: i64", x),
|
|
||||||
RuntimeValue::F32(x) => write!(f, "{}: f32", x),
|
|
||||||
RuntimeValue::F64(x) => write!(f, "{}: f64", x),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The result of invoking a wasm function or reading a wasm global.
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub enum ActionOutcome {
|
|
||||||
/// The action returned normally. Its return values are provided.
|
|
||||||
Returned {
|
|
||||||
/// The return values.
|
|
||||||
values: Vec<RuntimeValue>,
|
|
||||||
},
|
|
||||||
|
|
||||||
/// A trap occurred while the action was executing.
|
|
||||||
Trapped {
|
|
||||||
/// The trap message.
|
|
||||||
message: String,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
/// An error detected while invoking a wasm function or reading a wasm global.
|
|
||||||
/// Note that at this level, traps are not reported errors, but are rather
|
|
||||||
/// returned through `ActionOutcome`.
|
|
||||||
#[derive(Fail, Debug)]
|
|
||||||
pub enum ActionError {
|
|
||||||
/// No field with the specified name was present.
|
|
||||||
#[fail(display = "Unknown field: {}", _0)]
|
|
||||||
Field(String),
|
|
||||||
|
|
||||||
/// The field was present but was the wrong kind (eg. function, table, global, or memory).
|
|
||||||
#[fail(display = "Kind error: {}", _0)]
|
|
||||||
Kind(String),
|
|
||||||
|
|
||||||
/// The field was present but was the wrong type (eg. i32, i64, f32, or f64).
|
|
||||||
#[fail(display = "Type error: {}", _0)]
|
|
||||||
Type(String),
|
|
||||||
|
|
||||||
/// The module did not pass validation.
|
|
||||||
#[fail(display = "Validation error: {}", _0)]
|
|
||||||
Validate(String),
|
|
||||||
|
|
||||||
/// A wasm translation error occured.
|
|
||||||
#[fail(display = "WebAssembly compilation error: {}", _0)]
|
|
||||||
Compile(CompileError),
|
|
||||||
|
|
||||||
/// 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)]
|
|
||||||
Link(LinkError),
|
|
||||||
|
|
||||||
/// Start function trapped.
|
|
||||||
#[fail(display = "Start function trapped: {}", _0)]
|
|
||||||
Start(String),
|
|
||||||
}
|
|
||||||
@@ -1,288 +0,0 @@
|
|||||||
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::boxed::Box;
|
|
||||||
use std::cmp::max;
|
|
||||||
use std::rc::Rc;
|
|
||||||
use std::slice;
|
|
||||||
use std::string::String;
|
|
||||||
use std::vec::Vec;
|
|
||||||
use std::{mem, ptr};
|
|
||||||
use target_tunables::target_tunables;
|
|
||||||
use trampoline_park::TrampolinePark;
|
|
||||||
use wasmtime_environ::{
|
|
||||||
compile_module, Compilation, CompileError, DataInitializer, Module, ModuleEnvironment,
|
|
||||||
};
|
|
||||||
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();
|
|
||||||
let tunables = target_tunables(isa.triple());
|
|
||||||
|
|
||||||
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. And pack the values into memory
|
|
||||||
// instead of just using fixed-sized slots.
|
|
||||||
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 definition = match unsafe { self.instance.lookup_immutable(memory_name) } {
|
|
||||||
Some(Export::Memory {
|
|
||||||
definition,
|
|
||||||
memory: _memory,
|
|
||||||
vmctx: _vmctx,
|
|
||||||
}) => definition,
|
|
||||||
Some(_) => {
|
|
||||||
return Err(ActionError::Kind(format!(
|
|
||||||
"exported item \"{}\" is not a linear memory",
|
|
||||||
memory_name
|
|
||||||
)))
|
|
||||||
}
|
|
||||||
None => {
|
|
||||||
return Err(ActionError::Field(format!(
|
|
||||||
"no export named \"{}\"",
|
|
||||||
memory_name
|
|
||||||
)))
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(unsafe {
|
|
||||||
let memory_def = &*definition;
|
|
||||||
&slice::from_raw_parts(memory_def.base, memory_def.current_length)[start..start + len]
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Read a global in this `Instance` identified by an export name.
|
|
||||||
pub fn get(&self, global_name: &str) -> Result<RuntimeValue, ActionError> {
|
|
||||||
let (definition, global) = match unsafe { self.instance.lookup_immutable(global_name) } {
|
|
||||||
Some(Export::Global { definition, global }) => (definition, global),
|
|
||||||
Some(_) => {
|
|
||||||
return Err(ActionError::Kind(format!(
|
|
||||||
"exported item \"{}\" is not a global variable",
|
|
||||||
global_name
|
|
||||||
)))
|
|
||||||
}
|
|
||||||
None => {
|
|
||||||
return Err(ActionError::Field(format!(
|
|
||||||
"no export named \"{}\"",
|
|
||||||
global_name
|
|
||||||
)))
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
unsafe {
|
|
||||||
let global_def = &*definition;
|
|
||||||
Ok(match global.ty {
|
|
||||||
ir::types::I32 => RuntimeValue::I32(*global_def.as_i32()),
|
|
||||||
ir::types::I64 => RuntimeValue::I64(*global_def.as_i64()),
|
|
||||||
ir::types::F32 => RuntimeValue::F32(*global_def.as_f32_bits()),
|
|
||||||
ir::types::F64 => RuntimeValue::F64(*global_def.as_f64_bits()),
|
|
||||||
other => {
|
|
||||||
return Err(ActionError::Type(format!(
|
|
||||||
"global with type {} not supported",
|
|
||||||
other
|
|
||||||
)))
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
@@ -1,153 +0,0 @@
|
|||||||
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 std::vec::Vec;
|
|
||||||
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,5 +1,5 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "wasmtime-execute"
|
name = "wasmtime-jit"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
authors = ["The Cranelift Project Developers"]
|
authors = ["The Cranelift Project Developers"]
|
||||||
publish = false
|
publish = false
|
||||||
6
lib/jit/README.md
Normal file
6
lib/jit/README.md
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
This is the `wasmtime-jit` crate, which contains JIT-based execution
|
||||||
|
for wasm, using the wasm ABI defined by [`wasmtime-environ`] and the
|
||||||
|
runtime support provided by [`wasmtime-runtime`].
|
||||||
|
|
||||||
|
[`wasmtime-environ`]: https://crates.io/crates/wasmtime-environ
|
||||||
|
[`wasmtime-runtime`]: https://crates.io/crates/wasmtime-runtime
|
||||||
285
lib/jit/src/action.rs
Normal file
285
lib/jit/src/action.rs
Normal file
@@ -0,0 +1,285 @@
|
|||||||
|
//! Support for performing actions with a wasm module from the outside.
|
||||||
|
|
||||||
|
use compiler::Compiler;
|
||||||
|
use cranelift_codegen::ir;
|
||||||
|
use instantiate::SetupError;
|
||||||
|
use std::cmp::max;
|
||||||
|
use std::string::String;
|
||||||
|
use std::vec::Vec;
|
||||||
|
use std::{fmt, mem, ptr, slice};
|
||||||
|
use wasmtime_runtime::{wasmtime_call_trampoline, Export, Instance};
|
||||||
|
|
||||||
|
/// A runtime value.
|
||||||
|
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
|
||||||
|
pub enum RuntimeValue {
|
||||||
|
/// A runtime value with type i32.
|
||||||
|
I32(i32),
|
||||||
|
/// A runtime value with type i64.
|
||||||
|
I64(i64),
|
||||||
|
/// A runtime value with type f32.
|
||||||
|
F32(u32),
|
||||||
|
/// A runtime value with type f64.
|
||||||
|
F64(u64),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RuntimeValue {
|
||||||
|
/// Return the type of this `RuntimeValue`.
|
||||||
|
pub fn value_type(self) -> ir::Type {
|
||||||
|
match self {
|
||||||
|
RuntimeValue::I32(_) => ir::types::I32,
|
||||||
|
RuntimeValue::I64(_) => ir::types::I64,
|
||||||
|
RuntimeValue::F32(_) => ir::types::F32,
|
||||||
|
RuntimeValue::F64(_) => ir::types::F64,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Assuming this `RuntimeValue` holds an `i32`, return that value.
|
||||||
|
pub fn unwrap_i32(self) -> i32 {
|
||||||
|
match self {
|
||||||
|
RuntimeValue::I32(x) => x,
|
||||||
|
_ => panic!("unwrapping value of type {} as i32", self.value_type()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Assuming this `RuntimeValue` holds an `i64`, return that value.
|
||||||
|
pub fn unwrap_i64(self) -> i64 {
|
||||||
|
match self {
|
||||||
|
RuntimeValue::I64(x) => x,
|
||||||
|
_ => panic!("unwrapping value of type {} as i64", self.value_type()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Assuming this `RuntimeValue` holds an `f32`, return that value.
|
||||||
|
pub fn unwrap_f32(self) -> f32 {
|
||||||
|
f32::from_bits(self.unwrap_f32_bits())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Assuming this `RuntimeValue` holds an `f32`, return the bits of that value as a `u32`.
|
||||||
|
pub fn unwrap_f32_bits(self) -> u32 {
|
||||||
|
match self {
|
||||||
|
RuntimeValue::F32(x) => x,
|
||||||
|
_ => panic!("unwrapping value of type {} as f32", self.value_type()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Assuming this `RuntimeValue` holds an `f64`, return that value.
|
||||||
|
pub fn unwrap_f64(self) -> f64 {
|
||||||
|
f64::from_bits(self.unwrap_f64_bits())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Assuming this `RuntimeValue` holds an `f64`, return the bits of that value as a `u64`.
|
||||||
|
pub fn unwrap_f64_bits(self) -> u64 {
|
||||||
|
match self {
|
||||||
|
RuntimeValue::F64(x) => x,
|
||||||
|
_ => panic!("unwrapping value of type {} as f64", self.value_type()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for RuntimeValue {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
match self {
|
||||||
|
RuntimeValue::I32(x) => write!(f, "{}: i32", x),
|
||||||
|
RuntimeValue::I64(x) => write!(f, "{}: i64", x),
|
||||||
|
RuntimeValue::F32(x) => write!(f, "{}: f32", x),
|
||||||
|
RuntimeValue::F64(x) => write!(f, "{}: f64", x),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The result of invoking a wasm function or reading a wasm global.
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum ActionOutcome {
|
||||||
|
/// The action returned normally. Its return values are provided.
|
||||||
|
Returned {
|
||||||
|
/// The return values.
|
||||||
|
values: Vec<RuntimeValue>,
|
||||||
|
},
|
||||||
|
|
||||||
|
/// A trap occurred while the action was executing.
|
||||||
|
Trapped {
|
||||||
|
/// The trap message.
|
||||||
|
message: String,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
/// An error detected while invoking a wasm function or reading a wasm global.
|
||||||
|
/// Note that at this level, traps are not reported errors, but are rather
|
||||||
|
/// returned through `ActionOutcome`.
|
||||||
|
#[derive(Fail, Debug)]
|
||||||
|
pub enum ActionError {
|
||||||
|
/// An internal implementation error occurred.
|
||||||
|
#[fail(display = "{}", _0)]
|
||||||
|
Setup(SetupError),
|
||||||
|
|
||||||
|
/// No field with the specified name was present.
|
||||||
|
#[fail(display = "Unknown field: {}", _0)]
|
||||||
|
Field(String),
|
||||||
|
|
||||||
|
/// The field was present but was the wrong kind (eg. function, table, global, or memory).
|
||||||
|
#[fail(display = "Kind error: {}", _0)]
|
||||||
|
Kind(String),
|
||||||
|
|
||||||
|
/// The field was present but was the wrong type (eg. i32, i64, f32, or f64).
|
||||||
|
#[fail(display = "Type error: {}", _0)]
|
||||||
|
Type(String),
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Invoke a function in an `Instance` identified by an export name.
|
||||||
|
pub fn invoke(
|
||||||
|
compiler: &mut Compiler,
|
||||||
|
instance: &mut Instance,
|
||||||
|
function_name: &str,
|
||||||
|
args: &[RuntimeValue],
|
||||||
|
) -> Result<ActionOutcome, ActionError> {
|
||||||
|
let (address, signature, callee_vmctx) = match instance.lookup(function_name) {
|
||||||
|
Some(Export::Function {
|
||||||
|
address,
|
||||||
|
signature,
|
||||||
|
vmctx,
|
||||||
|
}) => (address, signature, vmctx),
|
||||||
|
Some(_) => {
|
||||||
|
return Err(ActionError::Kind(format!(
|
||||||
|
"exported item \"{}\" is not a function",
|
||||||
|
function_name
|
||||||
|
)))
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
return Err(ActionError::Field(format!(
|
||||||
|
"no export named \"{}\"",
|
||||||
|
function_name
|
||||||
|
)))
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
for (index, value) in args.iter().enumerate() {
|
||||||
|
assert_eq!(value.value_type(), signature.params[index].value_type);
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Support values larger than u64. And pack the values into memory
|
||||||
|
// instead of just using fixed-sized slots.
|
||||||
|
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 = compiler
|
||||||
|
.get_trampoline(address, &signature, value_size)
|
||||||
|
.map_err(ActionError::Setup)?;
|
||||||
|
|
||||||
|
// Make all JIT code produced thus far executable.
|
||||||
|
compiler.publish_compiled_code();
|
||||||
|
|
||||||
|
// Call the trampoline.
|
||||||
|
if let Err(message) = unsafe {
|
||||||
|
wasmtime_call_trampoline(
|
||||||
|
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<'instance>(
|
||||||
|
instance: &'instance Instance,
|
||||||
|
memory_name: &str,
|
||||||
|
start: usize,
|
||||||
|
len: usize,
|
||||||
|
) -> Result<&'instance [u8], ActionError> {
|
||||||
|
let definition = match unsafe { instance.lookup_immutable(memory_name) } {
|
||||||
|
Some(Export::Memory {
|
||||||
|
definition,
|
||||||
|
memory: _memory,
|
||||||
|
vmctx: _vmctx,
|
||||||
|
}) => definition,
|
||||||
|
Some(_) => {
|
||||||
|
return Err(ActionError::Kind(format!(
|
||||||
|
"exported item \"{}\" is not a linear memory",
|
||||||
|
memory_name
|
||||||
|
)))
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
return Err(ActionError::Field(format!(
|
||||||
|
"no export named \"{}\"",
|
||||||
|
memory_name
|
||||||
|
)))
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(unsafe {
|
||||||
|
let memory_def = &*definition;
|
||||||
|
&slice::from_raw_parts(memory_def.base, memory_def.current_length)[start..start + len]
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Read a global in this `Instance` identified by an export name.
|
||||||
|
pub fn get(instance: &Instance, global_name: &str) -> Result<RuntimeValue, ActionError> {
|
||||||
|
let (definition, global) = match unsafe { instance.lookup_immutable(global_name) } {
|
||||||
|
Some(Export::Global { definition, global }) => (definition, global),
|
||||||
|
Some(_) => {
|
||||||
|
return Err(ActionError::Kind(format!(
|
||||||
|
"exported item \"{}\" is not a global variable",
|
||||||
|
global_name
|
||||||
|
)))
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
return Err(ActionError::Field(format!(
|
||||||
|
"no export named \"{}\"",
|
||||||
|
global_name
|
||||||
|
)))
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
let global_def = &*definition;
|
||||||
|
Ok(match global.ty {
|
||||||
|
ir::types::I32 => RuntimeValue::I32(*global_def.as_i32()),
|
||||||
|
ir::types::I64 => RuntimeValue::I64(*global_def.as_i64()),
|
||||||
|
ir::types::F32 => RuntimeValue::F32(*global_def.as_f32_bits()),
|
||||||
|
ir::types::F64 => RuntimeValue::F64(*global_def.as_f64_bits()),
|
||||||
|
other => {
|
||||||
|
return Err(ActionError::Type(format!(
|
||||||
|
"global with type {} not supported",
|
||||||
|
other
|
||||||
|
)))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -7,15 +7,15 @@ use std::{cmp, mem};
|
|||||||
use wasmtime_runtime::{Mmap, VMFunctionBody};
|
use wasmtime_runtime::{Mmap, VMFunctionBody};
|
||||||
|
|
||||||
/// Memory manager for executable code.
|
/// Memory manager for executable code.
|
||||||
pub struct JITCode {
|
pub(crate) struct CodeMemory {
|
||||||
current: Mmap,
|
current: Mmap,
|
||||||
mmaps: Vec<Mmap>,
|
mmaps: Vec<Mmap>,
|
||||||
position: usize,
|
position: usize,
|
||||||
published: usize,
|
published: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl JITCode {
|
impl CodeMemory {
|
||||||
/// Create a new `JITCode` instance.
|
/// Create a new `CodeMemory` instance.
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Self {
|
Self {
|
||||||
current: Mmap::new(),
|
current: Mmap::new(),
|
||||||
252
lib/jit/src/compiler.rs
Normal file
252
lib/jit/src/compiler.rs
Normal file
@@ -0,0 +1,252 @@
|
|||||||
|
//! JIT compilation.
|
||||||
|
|
||||||
|
use code_memory::CodeMemory;
|
||||||
|
use cranelift_codegen::ir::InstBuilder;
|
||||||
|
use cranelift_codegen::isa::{TargetFrontendConfig, TargetIsa};
|
||||||
|
use cranelift_codegen::Context;
|
||||||
|
use cranelift_codegen::{binemit, ir};
|
||||||
|
use cranelift_entity::PrimaryMap;
|
||||||
|
use cranelift_frontend::{FunctionBuilder, FunctionBuilderContext};
|
||||||
|
use cranelift_wasm::DefinedFuncIndex;
|
||||||
|
use instantiate::SetupError;
|
||||||
|
use std::boxed::Box;
|
||||||
|
use std::collections::HashMap;
|
||||||
|
use std::string::String;
|
||||||
|
use std::vec::Vec;
|
||||||
|
use target_tunables::target_tunables;
|
||||||
|
use wasmtime_environ::cranelift;
|
||||||
|
use wasmtime_environ::{Compilation, CompileError, Module, Relocations, Tunables};
|
||||||
|
use wasmtime_runtime::{InstantiationError, VMFunctionBody};
|
||||||
|
|
||||||
|
/// A WebAssembly code JIT compiler.
|
||||||
|
///
|
||||||
|
/// A `Compiler` instance owns the executable memory that it allocates.
|
||||||
|
///
|
||||||
|
/// TODO: Evolve this to support streaming rather than requiring a `&[u8]`
|
||||||
|
/// containing a whole wasm module at once.
|
||||||
|
///
|
||||||
|
/// TODO: Consider using cranelift-module.
|
||||||
|
pub struct Compiler {
|
||||||
|
isa: Box<TargetIsa>,
|
||||||
|
|
||||||
|
code_memory: CodeMemory,
|
||||||
|
trampoline_park: HashMap<*const VMFunctionBody, *const VMFunctionBody>,
|
||||||
|
|
||||||
|
/// The `FunctionBuilderContext`, shared between trampline function compilations.
|
||||||
|
fn_builder_ctx: FunctionBuilderContext,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Compiler {
|
||||||
|
/// Construct a new `Compiler`.
|
||||||
|
pub fn new(isa: Box<TargetIsa>) -> Self {
|
||||||
|
Self {
|
||||||
|
isa,
|
||||||
|
code_memory: CodeMemory::new(),
|
||||||
|
trampoline_park: HashMap::new(),
|
||||||
|
fn_builder_ctx: FunctionBuilderContext::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Compiler {
|
||||||
|
/// Return the target's frontend configuration settings.
|
||||||
|
pub fn frontend_config(&self) -> TargetFrontendConfig {
|
||||||
|
self.isa.frontend_config()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return the tunables in use by this engine.
|
||||||
|
pub fn tunables(&self) -> Tunables {
|
||||||
|
target_tunables(self.isa.triple())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Compile the given function bodies.
|
||||||
|
pub(crate) fn compile<'data>(
|
||||||
|
&mut self,
|
||||||
|
module: &Module,
|
||||||
|
function_body_inputs: PrimaryMap<DefinedFuncIndex, &'data [u8]>,
|
||||||
|
) -> Result<
|
||||||
|
(
|
||||||
|
PrimaryMap<DefinedFuncIndex, *mut [VMFunctionBody]>,
|
||||||
|
Relocations,
|
||||||
|
),
|
||||||
|
SetupError,
|
||||||
|
> {
|
||||||
|
let (compilation, relocations) =
|
||||||
|
cranelift::compile_module(&module, function_body_inputs, &*self.isa)
|
||||||
|
.map_err(SetupError::Compile)?;
|
||||||
|
|
||||||
|
let allocated_functions =
|
||||||
|
allocate_functions(&mut self.code_memory, compilation).map_err(|message| {
|
||||||
|
SetupError::Instantiate(InstantiationError::Resource(format!(
|
||||||
|
"failed to allocate memory for functions: {}",
|
||||||
|
message
|
||||||
|
)))
|
||||||
|
})?;
|
||||||
|
|
||||||
|
Ok((allocated_functions, relocations))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create a trampoline for invoking a function.
|
||||||
|
pub(crate) fn get_trampoline(
|
||||||
|
&mut self,
|
||||||
|
callee_address: *const VMFunctionBody,
|
||||||
|
signature: &ir::Signature,
|
||||||
|
value_size: usize,
|
||||||
|
) -> Result<*const VMFunctionBody, SetupError> {
|
||||||
|
use std::collections::hash_map::Entry::{Occupied, Vacant};
|
||||||
|
Ok(match self.trampoline_park.entry(callee_address) {
|
||||||
|
Occupied(entry) => *entry.get(),
|
||||||
|
Vacant(entry) => {
|
||||||
|
let body = make_trampoline(
|
||||||
|
&*self.isa,
|
||||||
|
&mut self.code_memory,
|
||||||
|
&mut self.fn_builder_ctx,
|
||||||
|
callee_address,
|
||||||
|
signature,
|
||||||
|
value_size,
|
||||||
|
)?;
|
||||||
|
entry.insert(body);
|
||||||
|
body
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Make memory containing compiled code executable.
|
||||||
|
pub(crate) fn publish_compiled_code(&mut self) {
|
||||||
|
self.code_memory.publish();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create a trampoline for invoking a function.
|
||||||
|
fn make_trampoline(
|
||||||
|
isa: &TargetIsa,
|
||||||
|
code_memory: &mut CodeMemory,
|
||||||
|
fn_builder_ctx: &mut FunctionBuilderContext,
|
||||||
|
callee_address: *const VMFunctionBody,
|
||||||
|
signature: &ir::Signature,
|
||||||
|
value_size: usize,
|
||||||
|
) -> Result<*const VMFunctionBody, SetupError> {
|
||||||
|
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 (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 {};
|
||||||
|
let mut trap_sink = binemit::NullTrapSink {};
|
||||||
|
context
|
||||||
|
.compile_and_emit(isa, &mut code_buf, &mut reloc_sink, &mut trap_sink)
|
||||||
|
.map_err(|error| SetupError::Compile(CompileError::Codegen(error)))?;
|
||||||
|
|
||||||
|
Ok(code_memory
|
||||||
|
.allocate_copy_of_byte_slice(&code_buf)
|
||||||
|
.map_err(|message| SetupError::Instantiate(InstantiationError::Resource(message)))?
|
||||||
|
.as_ptr())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn allocate_functions(
|
||||||
|
code_memory: &mut CodeMemory,
|
||||||
|
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_memory.allocate_copy_of_byte_slice(body)?;
|
||||||
|
result.push(fatptr);
|
||||||
|
}
|
||||||
|
Ok(result)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// We don't expect trampoline compilation to produce any relocations, so
|
||||||
|
/// this `RelocSink` just asserts that it doesn't recieve any.
|
||||||
|
struct RelocSink {}
|
||||||
|
|
||||||
|
impl binemit::RelocSink for RelocSink {
|
||||||
|
fn reloc_ebb(
|
||||||
|
&mut self,
|
||||||
|
_offset: binemit::CodeOffset,
|
||||||
|
_reloc: binemit::Reloc,
|
||||||
|
_ebb_offset: binemit::CodeOffset,
|
||||||
|
) {
|
||||||
|
panic!("trampoline compilation should not produce ebb relocs");
|
||||||
|
}
|
||||||
|
fn reloc_external(
|
||||||
|
&mut self,
|
||||||
|
_offset: binemit::CodeOffset,
|
||||||
|
_reloc: binemit::Reloc,
|
||||||
|
_name: &ir::ExternalName,
|
||||||
|
_addend: binemit::Addend,
|
||||||
|
) {
|
||||||
|
panic!("trampoline compilation should not produce external symbol relocs");
|
||||||
|
}
|
||||||
|
fn reloc_jt(
|
||||||
|
&mut self,
|
||||||
|
_offset: binemit::CodeOffset,
|
||||||
|
_reloc: binemit::Reloc,
|
||||||
|
_jt: ir::JumpTable,
|
||||||
|
) {
|
||||||
|
panic!("trampoline compilation should not produce jump table relocs");
|
||||||
|
}
|
||||||
|
}
|
||||||
199
lib/jit/src/instantiate.rs
Normal file
199
lib/jit/src/instantiate.rs
Normal file
@@ -0,0 +1,199 @@
|
|||||||
|
//! Define the `instantiate` function, which takes a byte array containing an
|
||||||
|
//! encoded wasm module and returns a live wasm instance. Also, define
|
||||||
|
//! `CompiledModule` to allow compiling and instantiating to be done as separate
|
||||||
|
//! steps.
|
||||||
|
|
||||||
|
use compiler::Compiler;
|
||||||
|
use cranelift_entity::{BoxedSlice, PrimaryMap};
|
||||||
|
use cranelift_wasm::DefinedFuncIndex;
|
||||||
|
use link::link_module;
|
||||||
|
use resolver::Resolver;
|
||||||
|
use std::boxed::Box;
|
||||||
|
use std::rc::Rc;
|
||||||
|
use std::string::String;
|
||||||
|
use std::vec::Vec;
|
||||||
|
use wasmtime_environ::{
|
||||||
|
CompileError, DataInitializer, DataInitializerLocation, Module, ModuleEnvironment,
|
||||||
|
};
|
||||||
|
use wasmtime_runtime::{Imports, Instance, InstantiationError, VMFunctionBody};
|
||||||
|
|
||||||
|
/// An error condition while setting up a wasm instance, be it validation,
|
||||||
|
/// compilation, or instantiation.
|
||||||
|
#[derive(Fail, Debug)]
|
||||||
|
pub enum SetupError {
|
||||||
|
/// The module did not pass validation.
|
||||||
|
#[fail(display = "Validation error: {}", _0)]
|
||||||
|
Validate(String),
|
||||||
|
|
||||||
|
/// A wasm translation error occured.
|
||||||
|
#[fail(display = "WebAssembly compilation error: {}", _0)]
|
||||||
|
Compile(CompileError),
|
||||||
|
|
||||||
|
/// Some runtime resource was unavailable or insufficient, or the start function
|
||||||
|
/// trapped.
|
||||||
|
#[fail(display = "Instantiation error: {}", _0)]
|
||||||
|
Instantiate(InstantiationError),
|
||||||
|
}
|
||||||
|
|
||||||
|
/// This is similar to `CompiledModule`, but references the data initializers
|
||||||
|
/// from the wasm buffer rather than holding its own copy.
|
||||||
|
struct RawCompiledModule<'data> {
|
||||||
|
module: Module,
|
||||||
|
finished_functions: BoxedSlice<DefinedFuncIndex, *const VMFunctionBody>,
|
||||||
|
imports: Imports,
|
||||||
|
data_initializers: Box<[DataInitializer<'data>]>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'data> RawCompiledModule<'data> {
|
||||||
|
/// Create a new `RawCompiledModule` by compiling the wasm module in `data` and instatiating it.
|
||||||
|
fn new(
|
||||||
|
compiler: &mut Compiler,
|
||||||
|
data: &'data [u8],
|
||||||
|
resolver: &mut Resolver,
|
||||||
|
) -> Result<Self, SetupError> {
|
||||||
|
let environ = ModuleEnvironment::new(compiler.frontend_config(), compiler.tunables());
|
||||||
|
|
||||||
|
let translation = environ
|
||||||
|
.translate(data)
|
||||||
|
.map_err(|error| SetupError::Compile(CompileError::Wasm(error)))?;
|
||||||
|
|
||||||
|
let (allocated_functions, relocations) =
|
||||||
|
compiler.compile(&translation.module, translation.function_body_inputs)?;
|
||||||
|
|
||||||
|
let imports = link_module(
|
||||||
|
&translation.module,
|
||||||
|
&allocated_functions,
|
||||||
|
relocations,
|
||||||
|
resolver,
|
||||||
|
)
|
||||||
|
.map_err(|err| SetupError::Instantiate(InstantiationError::Link(err)))?;
|
||||||
|
|
||||||
|
// 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.
|
||||||
|
compiler.publish_compiled_code();
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
module: translation.module,
|
||||||
|
finished_functions,
|
||||||
|
imports,
|
||||||
|
data_initializers: translation.data_initializers.into_boxed_slice(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A compiled wasm module, ready to be instantiated.
|
||||||
|
pub struct CompiledModule {
|
||||||
|
module: Rc<Module>,
|
||||||
|
finished_functions: BoxedSlice<DefinedFuncIndex, *const VMFunctionBody>,
|
||||||
|
imports: Imports,
|
||||||
|
data_initializers: Box<[OwnedDataInitializer]>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CompiledModule {
|
||||||
|
/// Compile a data buffer into a `CompiledModule`, which may then be instantiated.
|
||||||
|
pub fn new<'data>(
|
||||||
|
compiler: &mut Compiler,
|
||||||
|
data: &'data [u8],
|
||||||
|
resolver: &mut Resolver,
|
||||||
|
) -> Result<Self, SetupError> {
|
||||||
|
let raw = RawCompiledModule::<'data>::new(compiler, data, resolver)?;
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
module: Rc::new(raw.module),
|
||||||
|
finished_functions: raw.finished_functions,
|
||||||
|
imports: raw.imports,
|
||||||
|
data_initializers: raw
|
||||||
|
.data_initializers
|
||||||
|
.iter()
|
||||||
|
.map(OwnedDataInitializer::new)
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
.into_boxed_slice(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Construct a `CompiledModule` from component parts.
|
||||||
|
pub fn from_parts(
|
||||||
|
module: Module,
|
||||||
|
finished_functions: BoxedSlice<DefinedFuncIndex, *const VMFunctionBody>,
|
||||||
|
imports: Imports,
|
||||||
|
data_initializers: Box<[OwnedDataInitializer]>,
|
||||||
|
) -> Self {
|
||||||
|
Self {
|
||||||
|
module: Rc::new(module),
|
||||||
|
finished_functions,
|
||||||
|
imports,
|
||||||
|
data_initializers,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Crate an `Instance` from this `CompiledModule`.
|
||||||
|
///
|
||||||
|
/// Note that if only one instance of this module is needed, it may be more
|
||||||
|
/// efficient to call the top-level `instantiate`, since that avoids copying
|
||||||
|
/// the data initializers.
|
||||||
|
pub fn instantiate(&mut self) -> Result<Box<Instance>, InstantiationError> {
|
||||||
|
let data_initializers = self
|
||||||
|
.data_initializers
|
||||||
|
.iter()
|
||||||
|
.map(|init| DataInitializer {
|
||||||
|
location: init.location.clone(),
|
||||||
|
data: &*init.data,
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
Instance::new(
|
||||||
|
Rc::clone(&self.module),
|
||||||
|
self.finished_functions.clone(),
|
||||||
|
self.imports.clone(),
|
||||||
|
&data_initializers,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Similar to `DataInitializer`, but owns its own copy of the data rather
|
||||||
|
/// than holding a slice of the original module.
|
||||||
|
pub struct OwnedDataInitializer {
|
||||||
|
/// The location where the initialization is to be performed.
|
||||||
|
location: DataInitializerLocation,
|
||||||
|
|
||||||
|
/// The initialization data.
|
||||||
|
data: Box<[u8]>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl OwnedDataInitializer {
|
||||||
|
fn new(borrowed: &DataInitializer) -> Self {
|
||||||
|
Self {
|
||||||
|
location: borrowed.location.clone(),
|
||||||
|
data: borrowed.data.to_vec().into_boxed_slice(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create a new `Instance` by compiling the wasm module in `data` and instatiating it.
|
||||||
|
///
|
||||||
|
/// This is equivalent to createing a `CompiledModule` and calling `instantiate()` on it,
|
||||||
|
/// but avoids creating an intermediate copy of the data initializers.
|
||||||
|
pub fn instantiate(
|
||||||
|
compiler: &mut Compiler,
|
||||||
|
data: &[u8],
|
||||||
|
resolver: &mut Resolver,
|
||||||
|
) -> Result<Box<Instance>, SetupError> {
|
||||||
|
let raw = RawCompiledModule::new(compiler, data, resolver)?;
|
||||||
|
|
||||||
|
Instance::new(
|
||||||
|
Rc::new(raw.module),
|
||||||
|
raw.finished_functions,
|
||||||
|
raw.imports,
|
||||||
|
&*raw.data_initializers,
|
||||||
|
)
|
||||||
|
.map_err(SetupError::Instantiate)
|
||||||
|
}
|
||||||
@@ -36,27 +36,31 @@ extern crate wasmtime_runtime;
|
|||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate alloc;
|
extern crate alloc;
|
||||||
extern crate failure;
|
extern crate failure;
|
||||||
|
extern crate target_lexicon;
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate failure_derive;
|
extern crate failure_derive;
|
||||||
extern crate target_lexicon;
|
|
||||||
|
|
||||||
mod action;
|
mod action;
|
||||||
mod instance_plus;
|
mod code_memory;
|
||||||
mod jit_code;
|
mod compiler;
|
||||||
|
mod instantiate;
|
||||||
mod link;
|
mod link;
|
||||||
mod namespace;
|
mod namespace;
|
||||||
mod resolver;
|
mod resolver;
|
||||||
mod target_tunables;
|
mod target_tunables;
|
||||||
mod trampoline_park;
|
|
||||||
|
|
||||||
pub use action::{ActionError, ActionOutcome, RuntimeValue};
|
pub use action::{ActionError, ActionOutcome, RuntimeValue};
|
||||||
pub use instance_plus::InstancePlus;
|
pub use compiler::Compiler;
|
||||||
pub use jit_code::JITCode;
|
pub use instantiate::{instantiate, CompiledModule, SetupError};
|
||||||
pub use link::link_module;
|
pub use link::link_module;
|
||||||
pub use namespace::{InstancePlusIndex, Namespace};
|
pub use namespace::{InstanceIndex, Namespace};
|
||||||
pub use resolver::{NullResolver, Resolver};
|
pub use resolver::{NullResolver, Resolver};
|
||||||
pub use target_tunables::target_tunables;
|
pub use target_tunables::target_tunables;
|
||||||
|
|
||||||
|
// Re-export `Instance` so that users won't need to separately depend on
|
||||||
|
// wasmtime-runtime in common cases.
|
||||||
|
pub use wasmtime_runtime::{Instance, InstantiationError};
|
||||||
|
|
||||||
#[cfg(not(feature = "std"))]
|
#[cfg(not(feature = "std"))]
|
||||||
mod std {
|
mod std {
|
||||||
pub use alloc::{boxed, rc, string, vec};
|
pub use alloc::{boxed, rc, string, vec};
|
||||||
@@ -1,24 +1,20 @@
|
|||||||
|
//! Linking for JIT-compiled code.
|
||||||
|
|
||||||
use cranelift_codegen::binemit::Reloc;
|
use cranelift_codegen::binemit::Reloc;
|
||||||
use cranelift_entity::PrimaryMap;
|
use cranelift_entity::PrimaryMap;
|
||||||
use cranelift_wasm::{DefinedFuncIndex, Global, GlobalInit, Memory, Table, TableElementType};
|
use cranelift_wasm::{DefinedFuncIndex, Global, GlobalInit, Memory, Table, TableElementType};
|
||||||
use resolver::Resolver;
|
use resolver::Resolver;
|
||||||
use std::ptr::write_unaligned;
|
use std::ptr::write_unaligned;
|
||||||
use std::string::String;
|
|
||||||
use std::vec::Vec;
|
use std::vec::Vec;
|
||||||
use wasmtime_environ::{
|
use wasmtime_environ::{
|
||||||
MemoryPlan, MemoryStyle, Module, Relocation, RelocationTarget, Relocations, TablePlan,
|
MemoryPlan, MemoryStyle, Module, Relocation, RelocationTarget, Relocations, TablePlan,
|
||||||
};
|
};
|
||||||
use wasmtime_runtime::libcalls;
|
use wasmtime_runtime::libcalls;
|
||||||
use wasmtime_runtime::{
|
use wasmtime_runtime::{
|
||||||
Export, Imports, VMFunctionBody, VMFunctionImport, VMGlobalImport, VMMemoryImport,
|
Export, Imports, LinkError, VMFunctionBody, VMFunctionImport, VMGlobalImport, VMMemoryImport,
|
||||||
VMTableImport,
|
VMTableImport,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// A link error, such as incompatible or unmatched imports/exports.
|
|
||||||
#[derive(Fail, Debug)]
|
|
||||||
#[fail(display = "Link error: {}", _0)]
|
|
||||||
pub struct LinkError(String);
|
|
||||||
|
|
||||||
/// Links a module that has been compiled with `compiled_module` in `wasmtime-environ`.
|
/// Links a module that has been compiled with `compiled_module` in `wasmtime-environ`.
|
||||||
pub fn link_module(
|
pub fn link_module(
|
||||||
module: &Module,
|
module: &Module,
|
||||||
@@ -2,19 +2,20 @@
|
|||||||
//! to exports. This file provides one possible way to manage multiple instances
|
//! to exports. This file provides one possible way to manage multiple instances
|
||||||
//! and resolve imports to exports among them.
|
//! and resolve imports to exports among them.
|
||||||
|
|
||||||
|
use action::{get, inspect_memory, invoke};
|
||||||
use action::{ActionError, ActionOutcome, RuntimeValue};
|
use action::{ActionError, ActionOutcome, RuntimeValue};
|
||||||
use cranelift_codegen::isa;
|
use compiler::Compiler;
|
||||||
use cranelift_entity::PrimaryMap;
|
use cranelift_entity::PrimaryMap;
|
||||||
use instance_plus::InstancePlus;
|
|
||||||
use jit_code::JITCode;
|
|
||||||
use resolver::Resolver;
|
use resolver::Resolver;
|
||||||
|
use std::boxed::Box;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use wasmtime_runtime::Export;
|
use std::string::String;
|
||||||
|
use wasmtime_runtime::{Export, Instance};
|
||||||
|
|
||||||
/// An opaque reference to an `InstancePlus`.
|
/// An opaque reference to an `Instance`.
|
||||||
#[derive(Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
|
#[derive(Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
|
||||||
pub struct InstancePlusIndex(u32);
|
pub struct InstanceIndex(u32);
|
||||||
entity_impl!(InstancePlusIndex, "instance");
|
entity_impl!(InstanceIndex, "instance");
|
||||||
|
|
||||||
/// A namespace containing instances keyed by name.
|
/// A namespace containing instances keyed by name.
|
||||||
///
|
///
|
||||||
@@ -22,10 +23,10 @@ entity_impl!(InstancePlusIndex, "instance");
|
|||||||
/// imports using defined exports.
|
/// imports using defined exports.
|
||||||
pub struct Namespace {
|
pub struct Namespace {
|
||||||
/// Mapping from identifiers to indices in `self.instances`.
|
/// Mapping from identifiers to indices in `self.instances`.
|
||||||
names: HashMap<String, InstancePlusIndex>,
|
names: HashMap<String, InstanceIndex>,
|
||||||
|
|
||||||
/// The instances, available by index.
|
/// The instances, available by index.
|
||||||
instances: PrimaryMap<InstancePlusIndex, InstancePlus>,
|
instances: PrimaryMap<InstanceIndex, Box<Instance>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Namespace {
|
impl Namespace {
|
||||||
@@ -37,13 +38,13 @@ impl Namespace {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Install a new `InstancePlus` in this `Namespace`, optionally with the
|
/// Install a new `Instance` in this `Namespace`, optionally with the
|
||||||
/// given name, and return its index.
|
/// given name, and return its index.
|
||||||
pub fn instance(
|
pub fn instance(
|
||||||
&mut self,
|
&mut self,
|
||||||
instance_name: Option<&str>,
|
instance_name: Option<&str>,
|
||||||
instance: InstancePlus,
|
instance: Box<Instance>,
|
||||||
) -> InstancePlusIndex {
|
) -> InstanceIndex {
|
||||||
let index = self.instances.push(instance);
|
let index = self.instances.push(instance);
|
||||||
if let Some(instance_name) = instance_name {
|
if let Some(instance_name) = instance_name {
|
||||||
self.names.insert(instance_name.into(), index);
|
self.names.insert(instance_name.into(), index);
|
||||||
@@ -52,41 +53,47 @@ impl Namespace {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Get the instance index registered with the given `instance_name`.
|
/// Get the instance index registered with the given `instance_name`.
|
||||||
pub fn get_instance_index(&mut self, instance_name: &str) -> Option<&mut InstancePlusIndex> {
|
pub fn get_instance_index(&mut self, instance_name: &str) -> Option<&mut InstanceIndex> {
|
||||||
self.names.get_mut(instance_name)
|
self.names.get_mut(instance_name)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Register an instance with a given name.
|
/// Register an instance with a given name.
|
||||||
pub fn register(&mut self, name: String, index: InstancePlusIndex) {
|
pub fn register(&mut self, name: String, index: InstanceIndex) {
|
||||||
self.names.insert(name, index);
|
self.names.insert(name, index);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Invoke an exported function from an instance.
|
/// Invoke an exported function from an instance.
|
||||||
pub fn invoke(
|
pub fn invoke(
|
||||||
&mut self,
|
&mut self,
|
||||||
jit_code: &mut JITCode,
|
compiler: &mut Compiler,
|
||||||
isa: &isa::TargetIsa,
|
index: InstanceIndex,
|
||||||
index: InstancePlusIndex,
|
|
||||||
field_name: &str,
|
field_name: &str,
|
||||||
args: &[RuntimeValue],
|
args: &[RuntimeValue],
|
||||||
) -> Result<ActionOutcome, ActionError> {
|
) -> Result<ActionOutcome, ActionError> {
|
||||||
self.instances[index].invoke(jit_code, isa, &field_name, &args)
|
invoke(compiler, &mut self.instances[index], &field_name, &args)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get a slice of memory from an instance.
|
||||||
|
pub fn inspect_memory(
|
||||||
|
&self,
|
||||||
|
index: InstanceIndex,
|
||||||
|
field_name: &str,
|
||||||
|
start: usize,
|
||||||
|
len: usize,
|
||||||
|
) -> Result<&[u8], ActionError> {
|
||||||
|
inspect_memory(&self.instances[index], &field_name, start, len)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get the value of an exported global from an instance.
|
/// Get the value of an exported global from an instance.
|
||||||
pub fn get(
|
pub fn get(&self, index: InstanceIndex, field_name: &str) -> Result<RuntimeValue, ActionError> {
|
||||||
&mut self,
|
get(&self.instances[index], &field_name)
|
||||||
index: InstancePlusIndex,
|
|
||||||
field_name: &str,
|
|
||||||
) -> Result<RuntimeValue, ActionError> {
|
|
||||||
self.instances[index].get(&field_name)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Resolver for Namespace {
|
impl Resolver for Namespace {
|
||||||
fn resolve(&mut self, instance: &str, field: &str) -> Option<Export> {
|
fn resolve(&mut self, instance: &str, field: &str) -> Option<Export> {
|
||||||
if let Some(index) = self.names.get(instance) {
|
if let Some(index) = self.names.get(instance) {
|
||||||
self.instances[*index].instance.lookup(field)
|
self.instances[*index].lookup(field)
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
@@ -1,3 +1,6 @@
|
|||||||
|
//! Define the `Resolver` trait, allowing custom resolution for external
|
||||||
|
//! references.
|
||||||
|
|
||||||
use wasmtime_runtime::Export;
|
use wasmtime_runtime::Export;
|
||||||
|
|
||||||
/// Import resolver connects imports with available exported values.
|
/// Import resolver connects imports with available exported values.
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
This is the `wasmtime-runtime` crate, which contains wasm runtime library
|
This is the `wasmtime-runtime` crate, which contains wasm runtime library
|
||||||
support, supporting the wasm ABI used by [`wasmtime-environ`],
|
support, supporting the wasm ABI used by [`wasmtime-environ`],
|
||||||
[`wasmtime-execute`], and [`wasmtime-obj`].
|
[`wasmtime-jit`], and [`wasmtime-obj`].
|
||||||
|
|
||||||
[`wasmtime-environ`]: https://crates.io/crates/wasmtime-environ
|
[`wasmtime-environ`]: https://crates.io/crates/wasmtime-environ
|
||||||
[`wasmtime-execute`]: https://crates.io/crates/wasmtime-execute
|
[`wasmtime-jit`]: https://crates.io/crates/wasmtime-jit
|
||||||
[`wasmtime-obj`]: https://crates.io/crates/wasmtime-obj
|
[`wasmtime-obj`]: https://crates.io/crates/wasmtime-obj
|
||||||
|
|||||||
@@ -640,9 +640,9 @@ WasmTrapHandler(int signum, siginfo_t* info, void* context)
|
|||||||
}
|
}
|
||||||
assert(previousSignal);
|
assert(previousSignal);
|
||||||
|
|
||||||
// This signal is not for any JIT code we expect, so we need to forward
|
// This signal is not for any compiled wasm code we expect, so we need to
|
||||||
// the signal to the next handler. If there is no next handler (SIG_IGN or
|
// forward the signal to the next handler. If there is no next handler (SIG_IGN
|
||||||
// SIG_DFL), then it's time to crash. To do this, we set the signal back to
|
// or SIG_DFL), then it's time to crash. To do this, we set the signal back to
|
||||||
// its original disposition and return. This will cause the faulting op to
|
// its original disposition and return. This will cause the faulting op to
|
||||||
// be re-executed which will crash in the normal way. The advantage of
|
// be re-executed which will crash in the normal way. The advantage of
|
||||||
// doing this to calling _exit() is that we remove ourselves from the crash
|
// doing this to calling _exit() is that we remove ourselves from the crash
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ use cranelift_wasm::{FuncIndex, GlobalIndex, MemoryIndex, TableIndex};
|
|||||||
use vmcontext::{VMFunctionImport, VMGlobalImport, VMMemoryImport, VMTableImport};
|
use vmcontext::{VMFunctionImport, VMGlobalImport, VMMemoryImport, VMTableImport};
|
||||||
|
|
||||||
/// Resolved import pointers.
|
/// Resolved import pointers.
|
||||||
#[derive(Debug)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct Imports {
|
pub struct Imports {
|
||||||
/// Resolved addresses for imported functions.
|
/// Resolved addresses for imported functions.
|
||||||
pub functions: BoxedSlice<FuncIndex, VMFunctionImport>,
|
pub functions: BoxedSlice<FuncIndex, VMFunctionImport>,
|
||||||
|
|||||||
@@ -23,6 +23,9 @@ use vmcontext::{
|
|||||||
use wasmtime_environ::{DataInitializer, Module};
|
use wasmtime_environ::{DataInitializer, Module};
|
||||||
|
|
||||||
/// An Instance of a WebAssemby module.
|
/// An Instance of a WebAssemby module.
|
||||||
|
///
|
||||||
|
/// Note that compiled wasm code passes around raw pointers to `Instance`, so
|
||||||
|
/// this shouldn't be moved.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Instance {
|
pub struct Instance {
|
||||||
/// The `Module` this `Instance` was instantiated from.
|
/// The `Module` this `Instance` was instantiated from.
|
||||||
@@ -53,7 +56,7 @@ pub struct Instance {
|
|||||||
/// WebAssembly global variable data.
|
/// WebAssembly global variable data.
|
||||||
vmctx_globals: BoxedSlice<DefinedGlobalIndex, VMGlobalDefinition>,
|
vmctx_globals: BoxedSlice<DefinedGlobalIndex, VMGlobalDefinition>,
|
||||||
|
|
||||||
/// Context pointer used by JIT code.
|
/// Context pointer used by compiled wasm code.
|
||||||
vmctx: VMContext,
|
vmctx: VMContext,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -63,7 +66,7 @@ impl Instance {
|
|||||||
module: Rc<Module>,
|
module: Rc<Module>,
|
||||||
finished_functions: BoxedSlice<DefinedFuncIndex, *const VMFunctionBody>,
|
finished_functions: BoxedSlice<DefinedFuncIndex, *const VMFunctionBody>,
|
||||||
mut vmctx_imports: Imports,
|
mut vmctx_imports: Imports,
|
||||||
data_initializers: Vec<DataInitializer>,
|
data_initializers: &[DataInitializer],
|
||||||
) -> Result<Box<Self>, InstantiationError> {
|
) -> Result<Box<Self>, InstantiationError> {
|
||||||
let mut sig_registry = create_and_initialize_signatures(&module);
|
let mut sig_registry = create_and_initialize_signatures(&module);
|
||||||
let mut tables = create_tables(&module);
|
let mut tables = create_tables(&module);
|
||||||
@@ -125,7 +128,7 @@ impl Instance {
|
|||||||
|
|
||||||
// Check initializer bounds before initializing anything.
|
// Check initializer bounds before initializing anything.
|
||||||
check_table_init_bounds(&mut *result)?;
|
check_table_init_bounds(&mut *result)?;
|
||||||
check_memory_init_bounds(&mut *result, &data_initializers)?;
|
check_memory_init_bounds(&mut *result, data_initializers)?;
|
||||||
|
|
||||||
// Apply the initializers.
|
// Apply the initializers.
|
||||||
initialize_tables(&mut *result)?;
|
initialize_tables(&mut *result)?;
|
||||||
@@ -148,22 +151,22 @@ impl Instance {
|
|||||||
Ok(result)
|
Ok(result)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return a reference to the vmctx used by JIT code.
|
/// Return a reference to the vmctx used by compiled wasm code.
|
||||||
pub fn vmctx(&self) -> &VMContext {
|
pub fn vmctx(&self) -> &VMContext {
|
||||||
&self.vmctx
|
&self.vmctx
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return a raw pointer to the vmctx used by JIT code.
|
/// Return a raw pointer to the vmctx used by compiled wasm code.
|
||||||
pub fn vmctx_ptr(&self) -> *const VMContext {
|
pub fn vmctx_ptr(&self) -> *const VMContext {
|
||||||
self.vmctx()
|
self.vmctx()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return a mutable reference to the vmctx used by JIT code.
|
/// Return a mutable reference to the vmctx used by compiled wasm code.
|
||||||
pub fn vmctx_mut(&mut self) -> &mut VMContext {
|
pub fn vmctx_mut(&mut self) -> &mut VMContext {
|
||||||
&mut self.vmctx
|
&mut self.vmctx
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return a mutable raw pointer to the vmctx used by JIT code.
|
/// Return a mutable raw pointer to the vmctx used by compiled wasm code.
|
||||||
pub fn vmctx_mut_ptr(&mut self) -> *mut VMContext {
|
pub fn vmctx_mut_ptr(&mut self) -> *mut VMContext {
|
||||||
self.vmctx_mut()
|
self.vmctx_mut()
|
||||||
}
|
}
|
||||||
@@ -184,7 +187,7 @@ impl Instance {
|
|||||||
.unwrap_or_else(|| panic!("no memory for index {}", memory_index.index()))
|
.unwrap_or_else(|| panic!("no memory for index {}", memory_index.index()))
|
||||||
.grow(delta);
|
.grow(delta);
|
||||||
|
|
||||||
// Keep current the VMContext pointers used by JIT code.
|
// Keep current the VMContext pointers used by compiled wasm code.
|
||||||
self.vmctx_memories[memory_index] = self.memories[memory_index].vmmemory();
|
self.vmctx_memories[memory_index] = self.memories[memory_index].vmmemory();
|
||||||
|
|
||||||
result
|
result
|
||||||
@@ -314,7 +317,7 @@ impl Instance {
|
|||||||
/// This requirement is not enforced in the type system, so this function is
|
/// This requirement is not enforced in the type system, so this function is
|
||||||
/// unsafe.
|
/// unsafe.
|
||||||
pub unsafe fn lookup_immutable(&self, field: &str) -> Option<Export> {
|
pub unsafe fn lookup_immutable(&self, field: &str) -> Option<Export> {
|
||||||
let temporary_mut = &mut *(self as *const Instance as *mut Instance);
|
let temporary_mut = &mut *(self as *const Self as *mut Self);
|
||||||
temporary_mut.lookup(field)
|
temporary_mut.lookup(field)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -346,9 +349,9 @@ fn check_table_init_bounds(instance: &mut Instance) -> Result<(), InstantiationE
|
|||||||
};
|
};
|
||||||
|
|
||||||
if slice.get_mut(start..start + init.elements.len()).is_none() {
|
if slice.get_mut(start..start + init.elements.len()).is_none() {
|
||||||
return Err(InstantiationError::Link(
|
return Err(InstantiationError::Link(LinkError(
|
||||||
"elements segment does not fit".to_owned(),
|
"elements segment does not fit".to_owned(),
|
||||||
));
|
)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -361,8 +364,8 @@ fn check_memory_init_bounds(
|
|||||||
) -> Result<(), InstantiationError> {
|
) -> Result<(), InstantiationError> {
|
||||||
for init in data_initializers {
|
for init in data_initializers {
|
||||||
// TODO: Refactor this.
|
// TODO: Refactor this.
|
||||||
let mut start = init.offset;
|
let mut start = init.location.offset;
|
||||||
if let Some(base) = init.base {
|
if let Some(base) = init.location.base {
|
||||||
let global = if let Some(def_index) = instance.module.defined_global_index(base) {
|
let global = if let Some(def_index) = instance.module.defined_global_index(base) {
|
||||||
unsafe { instance.vmctx.global_mut(def_index) }
|
unsafe { instance.vmctx.global_mut(def_index) }
|
||||||
} else {
|
} else {
|
||||||
@@ -372,12 +375,13 @@ fn check_memory_init_bounds(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Refactor this.
|
// TODO: Refactor this.
|
||||||
let memory = if let Some(defined_memory_index) =
|
let memory = if let Some(defined_memory_index) = instance
|
||||||
instance.module.defined_memory_index(init.memory_index)
|
.module
|
||||||
|
.defined_memory_index(init.location.memory_index)
|
||||||
{
|
{
|
||||||
unsafe { instance.vmctx.memory(defined_memory_index) }
|
unsafe { instance.vmctx.memory(defined_memory_index) }
|
||||||
} else {
|
} else {
|
||||||
let import = &instance.vmctx_imports.memories[init.memory_index];
|
let import = &instance.vmctx_imports.memories[init.location.memory_index];
|
||||||
let foreign_instance = unsafe { (&mut *(import).vmctx).instance() };
|
let foreign_instance = unsafe { (&mut *(import).vmctx).instance() };
|
||||||
let foreign_memory = unsafe { &mut *(import).from };
|
let foreign_memory = unsafe { &mut *(import).from };
|
||||||
let foreign_index = foreign_instance.vmctx().memory_index(foreign_memory);
|
let foreign_index = foreign_instance.vmctx().memory_index(foreign_memory);
|
||||||
@@ -386,9 +390,9 @@ fn check_memory_init_bounds(
|
|||||||
let mem_slice = unsafe { slice::from_raw_parts_mut(memory.base, memory.current_length) };
|
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() {
|
if mem_slice.get_mut(start..start + init.data.len()).is_none() {
|
||||||
return Err(InstantiationError::Link(
|
return Err(InstantiationError::Link(LinkError(
|
||||||
"data segment does not fit".to_owned(),
|
"data segment does not fit".to_owned(),
|
||||||
));
|
)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -457,9 +461,9 @@ fn initialize_tables(instance: &mut Instance) -> Result<(), InstantiationError>
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return Err(InstantiationError::Link(
|
return Err(InstantiationError::Link(LinkError(
|
||||||
"elements segment does not fit".to_owned(),
|
"elements segment does not fit".to_owned(),
|
||||||
));
|
)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -482,11 +486,11 @@ fn create_memories(
|
|||||||
/// Initialize the table memory from the provided initializers.
|
/// Initialize the table memory from the provided initializers.
|
||||||
fn initialize_memories(
|
fn initialize_memories(
|
||||||
instance: &mut Instance,
|
instance: &mut Instance,
|
||||||
data_initializers: Vec<DataInitializer>,
|
data_initializers: &[DataInitializer],
|
||||||
) -> Result<(), InstantiationError> {
|
) -> Result<(), InstantiationError> {
|
||||||
for init in data_initializers {
|
for init in data_initializers {
|
||||||
let mut start = init.offset;
|
let mut start = init.location.offset;
|
||||||
if let Some(base) = init.base {
|
if let Some(base) = init.location.base {
|
||||||
let global = if let Some(def_index) = instance.module.defined_global_index(base) {
|
let global = if let Some(def_index) = instance.module.defined_global_index(base) {
|
||||||
unsafe { instance.vmctx.global_mut(def_index) }
|
unsafe { instance.vmctx.global_mut(def_index) }
|
||||||
} else {
|
} else {
|
||||||
@@ -495,12 +499,13 @@ fn initialize_memories(
|
|||||||
start += unsafe { *(&*global).as_i32() } as u32 as usize;
|
start += unsafe { *(&*global).as_i32() } as u32 as usize;
|
||||||
}
|
}
|
||||||
|
|
||||||
let memory = if let Some(defined_memory_index) =
|
let memory = if let Some(defined_memory_index) = instance
|
||||||
instance.module.defined_memory_index(init.memory_index)
|
.module
|
||||||
|
.defined_memory_index(init.location.memory_index)
|
||||||
{
|
{
|
||||||
unsafe { instance.vmctx.memory(defined_memory_index) }
|
unsafe { instance.vmctx.memory(defined_memory_index) }
|
||||||
} else {
|
} else {
|
||||||
let import = &instance.vmctx_imports.memories[init.memory_index];
|
let import = &instance.vmctx_imports.memories[init.location.memory_index];
|
||||||
let foreign_instance = unsafe { (&mut *(import).vmctx).instance() };
|
let foreign_instance = unsafe { (&mut *(import).vmctx).instance() };
|
||||||
let foreign_memory = unsafe { &mut *(import).from };
|
let foreign_memory = unsafe { &mut *(import).from };
|
||||||
let foreign_index = foreign_instance.vmctx().memory_index(foreign_memory);
|
let foreign_index = foreign_instance.vmctx().memory_index(foreign_memory);
|
||||||
@@ -510,9 +515,9 @@ fn initialize_memories(
|
|||||||
if let Some(to_init) = mem_slice.get_mut(start..start + init.data.len()) {
|
if let Some(to_init) = mem_slice.get_mut(start..start + init.data.len()) {
|
||||||
to_init.copy_from_slice(init.data);
|
to_init.copy_from_slice(init.data);
|
||||||
} else {
|
} else {
|
||||||
return Err(InstantiationError::Link(
|
return Err(InstantiationError::Link(LinkError(
|
||||||
"data segment does not fit".to_owned(),
|
"data segment does not fit".to_owned(),
|
||||||
));
|
)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -555,6 +560,11 @@ fn initialize_globals(instance: &mut Instance) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// An link error while instantiating a module.
|
||||||
|
#[derive(Fail, Debug)]
|
||||||
|
#[fail(display = "Link error: {}", _0)]
|
||||||
|
pub struct LinkError(pub String);
|
||||||
|
|
||||||
/// An error while instantiating a module.
|
/// An error while instantiating a module.
|
||||||
#[derive(Fail, Debug)]
|
#[derive(Fail, Debug)]
|
||||||
pub enum InstantiationError {
|
pub enum InstantiationError {
|
||||||
@@ -562,9 +572,9 @@ pub enum InstantiationError {
|
|||||||
#[fail(display = "Insufficient resources: {}", _0)]
|
#[fail(display = "Insufficient resources: {}", _0)]
|
||||||
Resource(String),
|
Resource(String),
|
||||||
|
|
||||||
/// A wasm translation error occured.
|
/// A wasm link error occured.
|
||||||
#[fail(display = "Link error: {}", _0)]
|
#[fail(display = "{}", _0)]
|
||||||
Link(String),
|
Link(LinkError),
|
||||||
|
|
||||||
/// A compilation error occured.
|
/// A compilation error occured.
|
||||||
#[fail(display = "Trap occurred while invoking start function: {}", _0)]
|
#[fail(display = "Trap occurred while invoking start function: {}", _0)]
|
||||||
|
|||||||
@@ -60,7 +60,7 @@ pub mod libcalls;
|
|||||||
|
|
||||||
pub use export::Export;
|
pub use export::Export;
|
||||||
pub use imports::Imports;
|
pub use imports::Imports;
|
||||||
pub use instance::{Instance, InstantiationError};
|
pub use instance::{Instance, InstantiationError, LinkError};
|
||||||
pub use mmap::Mmap;
|
pub use mmap::Mmap;
|
||||||
pub use signalhandlers::{wasmtime_init_eager, wasmtime_init_finish};
|
pub use signalhandlers::{wasmtime_init_eager, wasmtime_init_finish};
|
||||||
pub use traphandlers::{wasmtime_call, wasmtime_call_trampoline};
|
pub use traphandlers::{wasmtime_call, wasmtime_call_trampoline};
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
//! Runtime library calls. Note that the JIT may sometimes perform these inline
|
//! Runtime library calls. Note that wasm compilers may sometimes perform these
|
||||||
//! rather than calling them, particularly when CPUs have special instructions
|
//! inline rather than calling them, particularly when CPUs have special
|
||||||
//! which compute them directly.
|
//! instructions which compute them directly.
|
||||||
|
|
||||||
use cranelift_wasm::{DefinedMemoryIndex, MemoryIndex};
|
use cranelift_wasm::{DefinedMemoryIndex, MemoryIndex};
|
||||||
use vmcontext::VMContext;
|
use vmcontext::VMContext;
|
||||||
|
|||||||
@@ -148,7 +148,7 @@ impl LinearMemory {
|
|||||||
Some(prev_pages)
|
Some(prev_pages)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return a `VMMemoryDefinition` for exposing the memory to JIT code.
|
/// Return a `VMMemoryDefinition` for exposing the memory to compiled wasm code.
|
||||||
pub fn vmmemory(&mut self) -> VMMemoryDefinition {
|
pub fn vmmemory(&mut self) -> VMMemoryDefinition {
|
||||||
VMMemoryDefinition {
|
VMMemoryDefinition {
|
||||||
base: self.mmap.as_mut_ptr(),
|
base: self.mmap.as_mut_ptr(),
|
||||||
|
|||||||
@@ -110,7 +110,7 @@ impl Mmap {
|
|||||||
self.ptr
|
self.ptr
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return the lengthof the allocated memory.
|
/// Return the length of the allocated memory.
|
||||||
pub fn len(&self) -> usize {
|
pub fn len(&self) -> usize {
|
||||||
self.len
|
self.len
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ impl Table {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return a `VMTableDefinition` for exposing the table to JIT code.
|
/// Return a `VMTableDefinition` for exposing the table to compiled wasm code.
|
||||||
pub fn vmtable(&mut self) -> VMTableDefinition {
|
pub fn vmtable(&mut self) -> VMTableDefinition {
|
||||||
VMTableDefinition {
|
VMTableDefinition {
|
||||||
base: self.vec.as_mut_ptr() as *mut u8,
|
base: self.vec.as_mut_ptr() as *mut u8,
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
//! This file declares `VMContext` and several related structs which contain
|
//! This file declares `VMContext` and several related structs which contain
|
||||||
//! fields that JIT code accesses directly.
|
//! fields that compiled wasm code accesses directly.
|
||||||
|
|
||||||
use cranelift_entity::EntityRef;
|
use cranelift_entity::EntityRef;
|
||||||
use cranelift_wasm::{
|
use cranelift_wasm::{
|
||||||
@@ -62,7 +62,7 @@ mod test_vmfunction_body {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The fields a JIT needs to access to utilize a WebAssembly table
|
/// The fields compiled code needs to access to utilize a WebAssembly table
|
||||||
/// imported from another instance.
|
/// imported from another instance.
|
||||||
#[derive(Debug, Copy, Clone)]
|
#[derive(Debug, Copy, Clone)]
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
@@ -98,7 +98,7 @@ mod test_vmtable_import {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The fields a JIT needs to access to utilize a WebAssembly linear
|
/// The fields compiled code needs to access to utilize a WebAssembly linear
|
||||||
/// memory imported from another instance.
|
/// memory imported from another instance.
|
||||||
#[derive(Debug, Copy, Clone)]
|
#[derive(Debug, Copy, Clone)]
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
@@ -134,7 +134,7 @@ mod test_vmmemory_import {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The fields a JIT needs to access to utilize a WebAssembly global
|
/// The fields compiled code needs to access to utilize a WebAssembly global
|
||||||
/// variable imported from another instance.
|
/// variable imported from another instance.
|
||||||
#[derive(Debug, Copy, Clone)]
|
#[derive(Debug, Copy, Clone)]
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
@@ -163,7 +163,7 @@ mod test_vmglobal_import {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The fields a JIT needs to access to utilize a WebAssembly linear
|
/// The fields compiled code needs to access to utilize a WebAssembly linear
|
||||||
/// memory defined within the instance, namely the start address and the
|
/// memory defined within the instance, namely the start address and the
|
||||||
/// size in bytes.
|
/// size in bytes.
|
||||||
#[derive(Debug, Copy, Clone)]
|
#[derive(Debug, Copy, Clone)]
|
||||||
@@ -206,7 +206,7 @@ mod test_vmmemory_definition {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The fields a JIT needs to access to utilize a WebAssembly table
|
/// The fields compiled code needs to access to utilize a WebAssembly table
|
||||||
/// defined within the instance.
|
/// defined within the instance.
|
||||||
#[derive(Debug, Copy, Clone)]
|
#[derive(Debug, Copy, Clone)]
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ cranelift-codegen = "0.26.0"
|
|||||||
cranelift-native = "0.26.0"
|
cranelift-native = "0.26.0"
|
||||||
cranelift-wasm = "0.26.0"
|
cranelift-wasm = "0.26.0"
|
||||||
cranelift-entity = "0.26.0"
|
cranelift-entity = "0.26.0"
|
||||||
wasmtime-execute = { path = "../execute" }
|
wasmtime-jit = { path = "../jit" }
|
||||||
wasmtime-runtime = { path = "../runtime" }
|
wasmtime-runtime = { path = "../runtime" }
|
||||||
wasmtime-environ = { path = "../environ" }
|
wasmtime-environ = { path = "../environ" }
|
||||||
wabt = "0.7"
|
wabt = "0.7"
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ extern crate target_lexicon;
|
|||||||
extern crate wabt;
|
extern crate wabt;
|
||||||
extern crate wasmparser;
|
extern crate wasmparser;
|
||||||
extern crate wasmtime_environ;
|
extern crate wasmtime_environ;
|
||||||
extern crate wasmtime_execute;
|
extern crate wasmtime_jit;
|
||||||
extern crate wasmtime_runtime;
|
extern crate wasmtime_runtime;
|
||||||
|
|
||||||
mod spectest;
|
mod spectest;
|
||||||
|
|||||||
@@ -2,13 +2,12 @@ use cranelift_codegen::ir::types;
|
|||||||
use cranelift_codegen::{ir, isa};
|
use cranelift_codegen::{ir, isa};
|
||||||
use cranelift_entity::PrimaryMap;
|
use cranelift_entity::PrimaryMap;
|
||||||
use cranelift_wasm::{DefinedFuncIndex, Global, GlobalInit, Memory, Table, TableElementType};
|
use cranelift_wasm::{DefinedFuncIndex, Global, GlobalInit, Memory, Table, TableElementType};
|
||||||
use std::rc::Rc;
|
|
||||||
use target_lexicon::HOST;
|
use target_lexicon::HOST;
|
||||||
use wasmtime_environ::{
|
use wasmtime_environ::{
|
||||||
translate_signature, Export, MemoryPlan, MemoryStyle, Module, TablePlan, TableStyle,
|
translate_signature, Export, MemoryPlan, MemoryStyle, Module, TablePlan, TableStyle,
|
||||||
};
|
};
|
||||||
use wasmtime_execute::{target_tunables, ActionError, InstancePlus};
|
use wasmtime_jit::{target_tunables, CompiledModule};
|
||||||
use wasmtime_runtime::{Imports, VMFunctionBody};
|
use wasmtime_runtime::{Imports, Instance, InstantiationError, VMFunctionBody};
|
||||||
|
|
||||||
extern "C" fn spectest_print() {}
|
extern "C" fn spectest_print() {}
|
||||||
|
|
||||||
@@ -46,7 +45,7 @@ extern "C" fn spectest_print_f64_f64(x: f64, y: f64) {
|
|||||||
|
|
||||||
/// Return an instance implementing the "spectest" interface used in the
|
/// Return an instance implementing the "spectest" interface used in the
|
||||||
/// spec testsuite.
|
/// spec testsuite.
|
||||||
pub fn instantiate_spectest() -> Result<InstancePlus, ActionError> {
|
pub fn instantiate_spectest() -> Result<Box<Instance>, InstantiationError> {
|
||||||
let call_conv = isa::CallConv::triple_default(&HOST);
|
let call_conv = isa::CallConv::triple_default(&HOST);
|
||||||
let pointer_type = types::Type::triple_pointer_type(&HOST);
|
let pointer_type = types::Type::triple_pointer_type(&HOST);
|
||||||
let mut module = Module::new();
|
let mut module = Module::new();
|
||||||
@@ -218,10 +217,11 @@ pub fn instantiate_spectest() -> Result<InstancePlus, ActionError> {
|
|||||||
let imports = Imports::none();
|
let imports = Imports::none();
|
||||||
let data_initializers = Vec::new();
|
let data_initializers = Vec::new();
|
||||||
|
|
||||||
InstancePlus::with_parts(
|
CompiledModule::from_parts(
|
||||||
Rc::new(module),
|
module,
|
||||||
finished_functions.into_boxed_slice(),
|
finished_functions.into_boxed_slice(),
|
||||||
imports,
|
imports,
|
||||||
data_initializers,
|
data_initializers.into_boxed_slice(),
|
||||||
)
|
)
|
||||||
|
.instantiate()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
use cranelift_codegen::isa;
|
|
||||||
use spectest::instantiate_spectest;
|
use spectest::instantiate_spectest;
|
||||||
use std::io::Read;
|
use std::io::Read;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use std::{fmt, fs, io, str};
|
use std::{fmt, fs, io, str};
|
||||||
use wabt::script::{Action, Command, CommandKind, ModuleBinary, ScriptParser, Value};
|
use wabt::script::{Action, Command, CommandKind, ModuleBinary, ScriptParser, Value};
|
||||||
use wasmparser::{validate, OperatorValidatorConfig, ValidatingParserConfig};
|
use wasmparser::{validate, OperatorValidatorConfig, ValidatingParserConfig};
|
||||||
use wasmtime_execute::{
|
use wasmtime_jit::{
|
||||||
ActionError, ActionOutcome, InstancePlus, InstancePlusIndex, JITCode, Namespace, RuntimeValue,
|
instantiate, ActionError, ActionOutcome, Compiler, Instance, InstanceIndex, InstantiationError,
|
||||||
|
Namespace, RuntimeValue, SetupError,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Translate from a script::Value to a RuntimeValue.
|
/// Translate from a script::Value to a RuntimeValue.
|
||||||
@@ -77,22 +77,22 @@ pub struct WastFileError {
|
|||||||
/// to be performed on them.
|
/// to be performed on them.
|
||||||
pub struct WastContext {
|
pub struct WastContext {
|
||||||
/// A namespace of wasm modules, keyed by an optional name.
|
/// A namespace of wasm modules, keyed by an optional name.
|
||||||
current: Option<InstancePlusIndex>,
|
current: Option<InstanceIndex>,
|
||||||
namespace: Namespace,
|
namespace: Namespace,
|
||||||
jit_code: JITCode,
|
compiler: Box<Compiler>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl WastContext {
|
impl WastContext {
|
||||||
/// Construct a new instance of `WastContext`.
|
/// Construct a new instance of `WastContext`.
|
||||||
pub fn new() -> Self {
|
pub fn new(compiler: Box<Compiler>) -> Self {
|
||||||
Self {
|
Self {
|
||||||
current: None,
|
current: None,
|
||||||
namespace: Namespace::new(),
|
namespace: Namespace::new(),
|
||||||
jit_code: JITCode::new(),
|
compiler,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn validate(&mut self, data: &[u8]) -> Result<(), ActionError> {
|
fn validate(&mut self, data: &[u8]) -> Result<(), String> {
|
||||||
let config = ValidatingParserConfig {
|
let config = ValidatingParserConfig {
|
||||||
operator_config: OperatorValidatorConfig {
|
operator_config: OperatorValidatorConfig {
|
||||||
enable_threads: false,
|
enable_threads: false,
|
||||||
@@ -107,26 +107,19 @@ impl WastContext {
|
|||||||
Ok(())
|
Ok(())
|
||||||
} else {
|
} else {
|
||||||
// TODO: Work with wasmparser to get better error messages.
|
// TODO: Work with wasmparser to get better error messages.
|
||||||
Err(ActionError::Validate("module did not validate".to_owned()))
|
Err("module did not validate".to_owned())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn instantiate(
|
fn instantiate(&mut self, module: ModuleBinary) -> Result<Box<Instance>, SetupError> {
|
||||||
&mut self,
|
|
||||||
isa: &isa::TargetIsa,
|
|
||||||
module: ModuleBinary,
|
|
||||||
) -> Result<InstancePlus, ActionError> {
|
|
||||||
let data = module.into_vec();
|
let data = module.into_vec();
|
||||||
|
|
||||||
self.validate(&data)?;
|
self.validate(&data).map_err(SetupError::Validate)?;
|
||||||
|
|
||||||
InstancePlus::new(&mut self.jit_code, isa, &data, &mut self.namespace)
|
instantiate(&mut *self.compiler, &data, &mut self.namespace)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_index(
|
fn get_index(&mut self, instance_name: &Option<String>) -> Result<InstanceIndex, WastError> {
|
||||||
&mut self,
|
|
||||||
instance_name: &Option<String>,
|
|
||||||
) -> Result<InstancePlusIndex, WastError> {
|
|
||||||
let index = *if let Some(instance_name) = instance_name {
|
let index = *if let Some(instance_name) = instance_name {
|
||||||
self.namespace
|
self.namespace
|
||||||
.get_instance_index(instance_name)
|
.get_instance_index(instance_name)
|
||||||
@@ -145,24 +138,20 @@ impl WastContext {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Register "spectest" which is used by the spec testsuite.
|
/// Register "spectest" which is used by the spec testsuite.
|
||||||
pub fn register_spectest(&mut self) -> Result<(), ActionError> {
|
pub fn register_spectest(&mut self) -> Result<(), InstantiationError> {
|
||||||
let instance = instantiate_spectest()?;
|
let instance = instantiate_spectest()?;
|
||||||
self.namespace.instance(Some("spectest"), instance);
|
self.namespace.instance(Some("spectest"), instance);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Perform the action portion of a command.
|
/// Perform the action portion of a command.
|
||||||
fn perform_action(
|
fn perform_action(&mut self, action: Action) -> Result<ActionOutcome, WastError> {
|
||||||
&mut self,
|
|
||||||
isa: &isa::TargetIsa,
|
|
||||||
action: Action,
|
|
||||||
) -> Result<ActionOutcome, WastError> {
|
|
||||||
match action {
|
match action {
|
||||||
Action::Invoke {
|
Action::Invoke {
|
||||||
module: instance_name,
|
module: instance_name,
|
||||||
field,
|
field,
|
||||||
args,
|
args,
|
||||||
} => self.invoke(isa, instance_name, &field, &args),
|
} => self.invoke(instance_name, &field, &args),
|
||||||
Action::Get {
|
Action::Get {
|
||||||
module: instance_name,
|
module: instance_name,
|
||||||
field,
|
field,
|
||||||
@@ -173,11 +162,10 @@ impl WastContext {
|
|||||||
/// Define a module and register it.
|
/// Define a module and register it.
|
||||||
fn module(
|
fn module(
|
||||||
&mut self,
|
&mut self,
|
||||||
isa: &isa::TargetIsa,
|
|
||||||
instance_name: Option<String>,
|
instance_name: Option<String>,
|
||||||
module: ModuleBinary,
|
module: ModuleBinary,
|
||||||
) -> Result<(), ActionError> {
|
) -> Result<(), ActionError> {
|
||||||
let instance = self.instantiate(isa, module)?;
|
let instance = self.instantiate(module).map_err(ActionError::Setup)?;
|
||||||
let index = self
|
let index = self
|
||||||
.namespace
|
.namespace
|
||||||
.instance(instance_name.as_ref().map(String::as_str), instance);
|
.instance(instance_name.as_ref().map(String::as_str), instance);
|
||||||
@@ -195,7 +183,6 @@ impl WastContext {
|
|||||||
/// Invoke an exported function from an instance.
|
/// Invoke an exported function from an instance.
|
||||||
fn invoke(
|
fn invoke(
|
||||||
&mut self,
|
&mut self,
|
||||||
isa: &isa::TargetIsa,
|
|
||||||
instance_name: Option<String>,
|
instance_name: Option<String>,
|
||||||
field: &str,
|
field: &str,
|
||||||
args: &[Value],
|
args: &[Value],
|
||||||
@@ -206,7 +193,7 @@ impl WastContext {
|
|||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
let index = self.get_index(&instance_name)?;
|
let index = self.get_index(&instance_name)?;
|
||||||
self.namespace
|
self.namespace
|
||||||
.invoke(&mut self.jit_code, isa, index, &field, &value_args)
|
.invoke(&mut *self.compiler, index, &field, &value_args)
|
||||||
.map_err(WastError::Action)
|
.map_err(WastError::Action)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -227,24 +214,15 @@ impl WastContext {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Perform the action of a `PerformAction`.
|
/// Perform the action of a `PerformAction`.
|
||||||
fn perform_action_command(
|
fn perform_action_command(&mut self, action: Action) -> Result<(), WastError> {
|
||||||
&mut self,
|
match self.perform_action(action)? {
|
||||||
isa: &isa::TargetIsa,
|
|
||||||
action: Action,
|
|
||||||
) -> Result<(), WastError> {
|
|
||||||
match self.perform_action(isa, action)? {
|
|
||||||
ActionOutcome::Returned { .. } => Ok(()),
|
ActionOutcome::Returned { .. } => Ok(()),
|
||||||
ActionOutcome::Trapped { message } => Err(WastError::Trap(message)),
|
ActionOutcome::Trapped { message } => Err(WastError::Trap(message)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Run a wast script from a byte buffer.
|
/// Run a wast script from a byte buffer.
|
||||||
pub fn run_buffer(
|
pub fn run_buffer(&mut self, filename: &str, wast: &[u8]) -> Result<(), WastFileError> {
|
||||||
&mut self,
|
|
||||||
isa: &isa::TargetIsa,
|
|
||||||
filename: &str,
|
|
||||||
wast: &[u8],
|
|
||||||
) -> Result<(), WastFileError> {
|
|
||||||
let mut parser = ScriptParser::from_str(str::from_utf8(wast).unwrap()).unwrap();
|
let mut parser = ScriptParser::from_str(str::from_utf8(wast).unwrap()).unwrap();
|
||||||
|
|
||||||
while let Some(Command { kind, line }) = parser.next().expect("parser") {
|
while let Some(Command { kind, line }) = parser.next().expect("parser") {
|
||||||
@@ -253,7 +231,7 @@ impl WastContext {
|
|||||||
module: instance_name,
|
module: instance_name,
|
||||||
name,
|
name,
|
||||||
} => {
|
} => {
|
||||||
self.module(isa, name, instance_name)
|
self.module(name, instance_name)
|
||||||
.map_err(|error| WastFileError {
|
.map_err(|error| WastFileError {
|
||||||
filename: filename.to_string(),
|
filename: filename.to_string(),
|
||||||
line,
|
line,
|
||||||
@@ -269,7 +247,7 @@ impl WastContext {
|
|||||||
})?;
|
})?;
|
||||||
}
|
}
|
||||||
CommandKind::PerformAction(action) => {
|
CommandKind::PerformAction(action) => {
|
||||||
self.perform_action_command(isa, action)
|
self.perform_action_command(action)
|
||||||
.map_err(|error| WastFileError {
|
.map_err(|error| WastFileError {
|
||||||
filename: filename.to_string(),
|
filename: filename.to_string(),
|
||||||
line,
|
line,
|
||||||
@@ -277,13 +255,11 @@ impl WastContext {
|
|||||||
})?;
|
})?;
|
||||||
}
|
}
|
||||||
CommandKind::AssertReturn { action, expected } => {
|
CommandKind::AssertReturn { action, expected } => {
|
||||||
match self
|
match self.perform_action(action).map_err(|error| WastFileError {
|
||||||
.perform_action(isa, action)
|
filename: filename.to_string(),
|
||||||
.map_err(|error| WastFileError {
|
line,
|
||||||
filename: filename.to_string(),
|
error,
|
||||||
line,
|
})? {
|
||||||
error,
|
|
||||||
})? {
|
|
||||||
ActionOutcome::Returned { values } => {
|
ActionOutcome::Returned { values } => {
|
||||||
for (v, e) in values
|
for (v, e) in values
|
||||||
.iter()
|
.iter()
|
||||||
@@ -312,13 +288,11 @@ impl WastContext {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
CommandKind::AssertTrap { action, message } => {
|
CommandKind::AssertTrap { action, message } => {
|
||||||
match self
|
match self.perform_action(action).map_err(|error| WastFileError {
|
||||||
.perform_action(isa, action)
|
filename: filename.to_string(),
|
||||||
.map_err(|error| WastFileError {
|
line,
|
||||||
filename: filename.to_string(),
|
error,
|
||||||
line,
|
})? {
|
||||||
error,
|
|
||||||
})? {
|
|
||||||
ActionOutcome::Returned { values } => {
|
ActionOutcome::Returned { values } => {
|
||||||
return Err(WastFileError {
|
return Err(WastFileError {
|
||||||
filename: filename.to_string(),
|
filename: filename.to_string(),
|
||||||
@@ -340,13 +314,11 @@ impl WastContext {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
CommandKind::AssertExhaustion { action } => {
|
CommandKind::AssertExhaustion { action } => {
|
||||||
match self
|
match self.perform_action(action).map_err(|error| WastFileError {
|
||||||
.perform_action(isa, action)
|
filename: filename.to_string(),
|
||||||
.map_err(|error| WastFileError {
|
line,
|
||||||
filename: filename.to_string(),
|
error,
|
||||||
line,
|
})? {
|
||||||
error,
|
|
||||||
})? {
|
|
||||||
ActionOutcome::Returned { values } => {
|
ActionOutcome::Returned { values } => {
|
||||||
return Err(WastFileError {
|
return Err(WastFileError {
|
||||||
filename: filename.to_string(),
|
filename: filename.to_string(),
|
||||||
@@ -366,13 +338,11 @@ impl WastContext {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
CommandKind::AssertReturnCanonicalNan { action } => {
|
CommandKind::AssertReturnCanonicalNan { action } => {
|
||||||
match self
|
match self.perform_action(action).map_err(|error| WastFileError {
|
||||||
.perform_action(isa, action)
|
filename: filename.to_string(),
|
||||||
.map_err(|error| WastFileError {
|
line,
|
||||||
filename: filename.to_string(),
|
error,
|
||||||
line,
|
})? {
|
||||||
error,
|
|
||||||
})? {
|
|
||||||
ActionOutcome::Returned { values } => {
|
ActionOutcome::Returned { values } => {
|
||||||
for v in values.iter() {
|
for v in values.iter() {
|
||||||
match v {
|
match v {
|
||||||
@@ -420,13 +390,11 @@ impl WastContext {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
CommandKind::AssertReturnArithmeticNan { action } => {
|
CommandKind::AssertReturnArithmeticNan { action } => {
|
||||||
match self
|
match self.perform_action(action).map_err(|error| WastFileError {
|
||||||
.perform_action(isa, action)
|
filename: filename.to_string(),
|
||||||
.map_err(|error| WastFileError {
|
line,
|
||||||
filename: filename.to_string(),
|
error,
|
||||||
line,
|
})? {
|
||||||
error,
|
|
||||||
})? {
|
|
||||||
ActionOutcome::Returned { values } => {
|
ActionOutcome::Returned { values } => {
|
||||||
for v in values.iter() {
|
for v in values.iter() {
|
||||||
match v {
|
match v {
|
||||||
@@ -474,7 +442,7 @@ impl WastContext {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
CommandKind::AssertInvalid { module, message } => {
|
CommandKind::AssertInvalid { module, message } => {
|
||||||
self.module(isa, None, module).expect_err(&format!(
|
self.module(None, module).expect_err(&format!(
|
||||||
"{}:{}: invalid module was successfully instantiated",
|
"{}:{}: invalid module was successfully instantiated",
|
||||||
filename, line
|
filename, line
|
||||||
));
|
));
|
||||||
@@ -484,7 +452,7 @@ impl WastContext {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
CommandKind::AssertMalformed { module, message } => {
|
CommandKind::AssertMalformed { module, message } => {
|
||||||
self.module(isa, None, module).expect_err(&format!(
|
self.module(None, module).expect_err(&format!(
|
||||||
"{}:{}: malformed module was successfully instantiated",
|
"{}:{}: malformed module was successfully instantiated",
|
||||||
filename, line
|
filename, line
|
||||||
));
|
));
|
||||||
@@ -494,7 +462,7 @@ impl WastContext {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
CommandKind::AssertUninstantiable { module, message } => {
|
CommandKind::AssertUninstantiable { module, message } => {
|
||||||
let _err = self.module(isa, None, module).expect_err(&format!(
|
let _err = self.module(None, module).expect_err(&format!(
|
||||||
"{}:{}: uninstantiable module was successfully instantiated",
|
"{}:{}: uninstantiable module was successfully instantiated",
|
||||||
filename, line
|
filename, line
|
||||||
));
|
));
|
||||||
@@ -504,7 +472,7 @@ impl WastContext {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
CommandKind::AssertUnlinkable { module, message } => {
|
CommandKind::AssertUnlinkable { module, message } => {
|
||||||
let _err = self.module(isa, None, module).expect_err(&format!(
|
let _err = self.module(None, module).expect_err(&format!(
|
||||||
"{}:{}: unlinkable module was successfully linked",
|
"{}:{}: unlinkable module was successfully linked",
|
||||||
filename, line
|
filename, line
|
||||||
));
|
));
|
||||||
@@ -520,14 +488,14 @@ impl WastContext {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Run a wast script from a file.
|
/// Run a wast script from a file.
|
||||||
pub fn run_file(&mut self, isa: &isa::TargetIsa, path: &Path) -> Result<(), WastFileError> {
|
pub fn run_file(&mut self, path: &Path) -> Result<(), WastFileError> {
|
||||||
let filename = path.display().to_string();
|
let filename = path.display().to_string();
|
||||||
let buffer = read_to_end(path).map_err(|e| WastFileError {
|
let buffer = read_to_end(path).map_err(|e| WastFileError {
|
||||||
filename,
|
filename,
|
||||||
line: 0,
|
line: 0,
|
||||||
error: WastError::IO(e),
|
error: WastError::IO(e),
|
||||||
})?;
|
})?;
|
||||||
self.run_buffer(isa, &path.display().to_string(), &buffer)
|
self.run_buffer(&path.display().to_string(), &buffer)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ use std::path::PathBuf;
|
|||||||
use std::process;
|
use std::process;
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
use target_lexicon::Triple;
|
use target_lexicon::Triple;
|
||||||
use wasmtime_environ::{compile_module, Module, ModuleEnvironment, Tunables};
|
use wasmtime_environ::{cranelift, ModuleEnvironment, Tunables};
|
||||||
use wasmtime_obj::emit_module;
|
use wasmtime_obj::emit_module;
|
||||||
|
|
||||||
const USAGE: &str = "
|
const USAGE: &str = "
|
||||||
@@ -133,30 +133,38 @@ fn handle_module(path: PathBuf, target: &Option<String>, output: &str) -> Result
|
|||||||
|
|
||||||
let mut obj = Artifact::new(isa.triple().clone(), String::from(output));
|
let mut obj = Artifact::new(isa.triple().clone(), String::from(output));
|
||||||
|
|
||||||
let mut module = Module::new();
|
|
||||||
// TODO: Expose the tunables as command-line flags.
|
// TODO: Expose the tunables as command-line flags.
|
||||||
let tunables = Tunables::default();
|
let tunables = Tunables::default();
|
||||||
let environ = ModuleEnvironment::new(&*isa, &mut module, tunables);
|
|
||||||
let translation = environ.translate(&data).map_err(|e| e.to_string())?;
|
let (module, lazy_function_body_inputs, lazy_data_initializers) = {
|
||||||
|
let environ = ModuleEnvironment::new(isa.frontend_config(), tunables);
|
||||||
|
|
||||||
|
let translation = environ
|
||||||
|
.translate(&data)
|
||||||
|
.map_err(|error| error.to_string())?;
|
||||||
|
|
||||||
|
(
|
||||||
|
translation.module,
|
||||||
|
translation.function_body_inputs,
|
||||||
|
translation.data_initializers,
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
// FIXME: We need to initialize memory in a way that supports alternate
|
// FIXME: We need to initialize memory in a way that supports alternate
|
||||||
// memory spaces, imported base addresses, and offsets.
|
// memory spaces, imported base addresses, and offsets.
|
||||||
for init in &translation.lazy.data_initializers {
|
for init in lazy_data_initializers.into_iter() {
|
||||||
obj.define("memory", Vec::from(init.data))
|
obj.define("memory", Vec::from(init.data))
|
||||||
.map_err(|err| format!("{}", err))?;
|
.map_err(|err| format!("{}", err))?;
|
||||||
}
|
}
|
||||||
|
|
||||||
let (compilation, relocations) = compile_module(
|
let (compilation, relocations) =
|
||||||
&translation.module,
|
cranelift::compile_module(&module, lazy_function_body_inputs, &*isa)
|
||||||
&translation.lazy.function_body_inputs,
|
.map_err(|e| e.to_string())?;
|
||||||
&*isa,
|
|
||||||
)
|
|
||||||
.map_err(|e| e.to_string())?;
|
|
||||||
|
|
||||||
emit_module(&mut obj, &translation.module, &compilation, &relocations)?;
|
emit_module(&mut obj, &module, &compilation, &relocations)?;
|
||||||
|
|
||||||
if !translation.module.table_plans.is_empty() {
|
if !module.table_plans.is_empty() {
|
||||||
if translation.module.table_plans.len() > 1 {
|
if module.table_plans.len() > 1 {
|
||||||
return Err(String::from("multiple tables not supported yet"));
|
return Err(String::from("multiple tables not supported yet"));
|
||||||
}
|
}
|
||||||
return Err(String::from("FIXME: implement tables"));
|
return Err(String::from("FIXME: implement tables"));
|
||||||
|
|||||||
@@ -33,7 +33,7 @@
|
|||||||
extern crate cranelift_codegen;
|
extern crate cranelift_codegen;
|
||||||
extern crate cranelift_native;
|
extern crate cranelift_native;
|
||||||
extern crate docopt;
|
extern crate docopt;
|
||||||
extern crate wasmtime_execute;
|
extern crate wasmtime_jit;
|
||||||
extern crate wasmtime_wast;
|
extern crate wasmtime_wast;
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate serde_derive;
|
extern crate serde_derive;
|
||||||
@@ -41,7 +41,6 @@ extern crate file_per_thread_logger;
|
|||||||
extern crate pretty_env_logger;
|
extern crate pretty_env_logger;
|
||||||
extern crate wabt;
|
extern crate wabt;
|
||||||
|
|
||||||
use cranelift_codegen::isa::TargetIsa;
|
|
||||||
use cranelift_codegen::settings;
|
use cranelift_codegen::settings;
|
||||||
use cranelift_codegen::settings::Configurable;
|
use cranelift_codegen::settings::Configurable;
|
||||||
use docopt::Docopt;
|
use docopt::Docopt;
|
||||||
@@ -52,7 +51,7 @@ use std::io::prelude::*;
|
|||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use std::process::exit;
|
use std::process::exit;
|
||||||
use wasmtime_execute::{ActionOutcome, InstancePlus, JITCode, Namespace};
|
use wasmtime_jit::{instantiate, ActionOutcome, Compiler, Namespace};
|
||||||
use wasmtime_wast::instantiate_spectest;
|
use wasmtime_wast::instantiate_spectest;
|
||||||
|
|
||||||
static LOG_FILENAME_PREFIX: &str = "wasmtime.dbg.";
|
static LOG_FILENAME_PREFIX: &str = "wasmtime.dbg.";
|
||||||
@@ -123,6 +122,7 @@ fn main() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let isa = isa_builder.finish(settings::Flags::new(flag_builder));
|
let isa = isa_builder.finish(settings::Flags::new(flag_builder));
|
||||||
|
let mut compiler = Compiler::new(isa);
|
||||||
|
|
||||||
let mut namespace = Namespace::new();
|
let mut namespace = Namespace::new();
|
||||||
|
|
||||||
@@ -132,11 +132,9 @@ fn main() {
|
|||||||
instantiate_spectest().expect("instantiating spectest"),
|
instantiate_spectest().expect("instantiating spectest"),
|
||||||
);
|
);
|
||||||
|
|
||||||
let mut jit_code = JITCode::new();
|
|
||||||
|
|
||||||
for filename in &args.arg_file {
|
for filename in &args.arg_file {
|
||||||
let path = Path::new(&filename);
|
let path = Path::new(&filename);
|
||||||
match handle_module(&mut jit_code, &mut namespace, &args, path, &*isa) {
|
match handle_module(&mut compiler, &mut namespace, &args, path) {
|
||||||
Ok(()) => {}
|
Ok(()) => {}
|
||||||
Err(message) => {
|
Err(message) => {
|
||||||
let name = path.as_os_str().to_string_lossy();
|
let name = path.as_os_str().to_string_lossy();
|
||||||
@@ -148,11 +146,10 @@ fn main() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn handle_module(
|
fn handle_module(
|
||||||
jit_code: &mut JITCode,
|
compiler: &mut Compiler,
|
||||||
namespace: &mut Namespace,
|
namespace: &mut Namespace,
|
||||||
args: &Args,
|
args: &Args,
|
||||||
path: &Path,
|
path: &Path,
|
||||||
isa: &TargetIsa,
|
|
||||||
) -> Result<(), String> {
|
) -> Result<(), String> {
|
||||||
let mut data =
|
let mut data =
|
||||||
read_to_end(path.to_path_buf()).map_err(|err| String::from(err.description()))?;
|
read_to_end(path.to_path_buf()).map_err(|err| String::from(err.description()))?;
|
||||||
@@ -162,17 +159,16 @@ fn handle_module(
|
|||||||
data = wabt::wat2wasm(data).map_err(|err| String::from(err.description()))?;
|
data = wabt::wat2wasm(data).map_err(|err| String::from(err.description()))?;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create a new `InstancePlus` by compiling and instantiating a wasm module.
|
// Create a new `Instance` by compiling and instantiating a wasm module.
|
||||||
let instance_plus =
|
let instance = instantiate(compiler, &data, namespace).map_err(|e| e.to_string())?;
|
||||||
InstancePlus::new(jit_code, isa, &data, namespace).map_err(|e| e.to_string())?;
|
|
||||||
|
|
||||||
// Register it in the namespace.
|
// Register it in the namespace.
|
||||||
let index = namespace.instance(None, instance_plus);
|
let index = namespace.instance(None, instance);
|
||||||
|
|
||||||
// If a function to invoke was given, invoke it.
|
// If a function to invoke was given, invoke it.
|
||||||
if let Some(ref f) = args.flag_invoke {
|
if let Some(ref f) = args.flag_invoke {
|
||||||
match namespace
|
match namespace
|
||||||
.invoke(jit_code, isa, index, &f, &[])
|
.invoke(compiler, index, &f, &[])
|
||||||
.map_err(|e| e.to_string())?
|
.map_err(|e| e.to_string())?
|
||||||
{
|
{
|
||||||
ActionOutcome::Returned { .. } => {}
|
ActionOutcome::Returned { .. } => {}
|
||||||
@@ -191,7 +187,7 @@ mod tests {
|
|||||||
use cranelift_codegen::settings::Configurable;
|
use cranelift_codegen::settings::Configurable;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use wabt;
|
use wabt;
|
||||||
use wasmtime_execute::{InstancePlus, JITCode, NullResolver};
|
use wasmtime_jit::{instantiate, Compiler, NullResolver};
|
||||||
|
|
||||||
const PATH_MODULE_RS2WASM_ADD_FUNC: &str = r"filetests/rs2wasm-add-func.wat";
|
const PATH_MODULE_RS2WASM_ADD_FUNC: &str = r"filetests/rs2wasm-add-func.wat";
|
||||||
|
|
||||||
@@ -214,8 +210,8 @@ mod tests {
|
|||||||
let isa = isa_builder.finish(settings::Flags::new(flag_builder));
|
let isa = isa_builder.finish(settings::Flags::new(flag_builder));
|
||||||
|
|
||||||
let mut resolver = NullResolver {};
|
let mut resolver = NullResolver {};
|
||||||
let mut code = JITCode::new();
|
let mut compiler = Compiler::new(isa);
|
||||||
let instance = InstancePlus::new(&mut code, &*isa, &data, &mut resolver);
|
let instance = instantiate(&mut compiler, &data, &mut resolver);
|
||||||
assert!(instance.is_ok());
|
assert!(instance.is_ok());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,6 +28,7 @@
|
|||||||
extern crate cranelift_codegen;
|
extern crate cranelift_codegen;
|
||||||
extern crate cranelift_native;
|
extern crate cranelift_native;
|
||||||
extern crate docopt;
|
extern crate docopt;
|
||||||
|
extern crate wasmtime_jit;
|
||||||
extern crate wasmtime_wast;
|
extern crate wasmtime_wast;
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate serde_derive;
|
extern crate serde_derive;
|
||||||
@@ -38,6 +39,7 @@ use cranelift_codegen::settings;
|
|||||||
use cranelift_codegen::settings::Configurable;
|
use cranelift_codegen::settings::Configurable;
|
||||||
use docopt::Docopt;
|
use docopt::Docopt;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
use wasmtime_jit::Compiler;
|
||||||
use wasmtime_wast::WastContext;
|
use wasmtime_wast::WastContext;
|
||||||
|
|
||||||
static LOG_FILENAME_PREFIX: &str = "cranelift.dbg.";
|
static LOG_FILENAME_PREFIX: &str = "cranelift.dbg.";
|
||||||
@@ -94,7 +96,8 @@ fn main() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let isa = isa_builder.finish(settings::Flags::new(flag_builder));
|
let isa = isa_builder.finish(settings::Flags::new(flag_builder));
|
||||||
let mut wast_context = WastContext::new();
|
let engine = Compiler::new(isa);
|
||||||
|
let mut wast_context = WastContext::new(Box::new(engine));
|
||||||
|
|
||||||
wast_context
|
wast_context
|
||||||
.register_spectest()
|
.register_spectest()
|
||||||
@@ -102,7 +105,7 @@ fn main() {
|
|||||||
|
|
||||||
for filename in &args.arg_file {
|
for filename in &args.arg_file {
|
||||||
wast_context
|
wast_context
|
||||||
.run_file(&*isa, Path::new(&filename))
|
.run_file(Path::new(&filename))
|
||||||
.unwrap_or_else(|e| panic!("{}", e));
|
.unwrap_or_else(|e| panic!("{}", e));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,13 @@
|
|||||||
extern crate cranelift_codegen;
|
extern crate cranelift_codegen;
|
||||||
extern crate cranelift_native;
|
extern crate cranelift_native;
|
||||||
|
extern crate wasmtime_jit;
|
||||||
extern crate wasmtime_wast;
|
extern crate wasmtime_wast;
|
||||||
|
|
||||||
use cranelift_codegen::isa;
|
use cranelift_codegen::isa;
|
||||||
use cranelift_codegen::settings;
|
use cranelift_codegen::settings;
|
||||||
use cranelift_codegen::settings::Configurable;
|
use cranelift_codegen::settings::Configurable;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
use wasmtime_jit::Compiler;
|
||||||
use wasmtime_wast::WastContext;
|
use wasmtime_wast::WastContext;
|
||||||
|
|
||||||
include!(concat!(env!("OUT_DIR"), "/wast_testsuite_tests.rs"));
|
include!(concat!(env!("OUT_DIR"), "/wast_testsuite_tests.rs"));
|
||||||
|
|||||||
Reference in New Issue
Block a user