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:
Dan Gohman
2019-01-03 06:59:46 -08:00
parent 450a279e18
commit 7592c99f3b
46 changed files with 1225 additions and 1073 deletions

View File

@@ -28,7 +28,7 @@ cranelift-entity = "0.26.0"
cranelift-wasm = "0.26.0"
wasmtime-environ = { path = "lib/environ" }
wasmtime-runtime = { path = "lib/runtime" }
wasmtime-execute = { path = "lib/execute" }
wasmtime-jit = { path = "lib/jit" }
wasmtime-obj = { path = "lib/obj" }
wasmtime-wast = { path = "lib/wast" }
docopt = "1.0.1"

View File

@@ -53,7 +53,10 @@ fn test_directory(out: &mut File, testsuite: &str) -> io::Result<()> {
.expect("testsuite filename should be representable as a string")
.replace("-", "_")
)?;
writeln!(out, " use super::{{native_isa, Path, WastContext}};")?;
writeln!(
out,
" use super::{{native_isa, Path, WastContext, Compiler}};"
)?;
for dir_entry in dir_entries {
write_testsuite_tests(out, dir_entry, testsuite)?;
}
@@ -78,8 +81,12 @@ fn write_testsuite_tests(out: &mut File, dir_entry: DirEntry, testsuite: &str) -
" fn {}() {{",
avoid_keywords(&stemstr.replace("-", "_"))
)?;
writeln!(out, " let mut wast_context = WastContext::new();")?;
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, " .register_spectest()")?;
writeln!(
@@ -87,7 +94,7 @@ fn write_testsuite_tests(out: &mut File, dir_entry: DirEntry, testsuite: &str) -
" .expect(\"instantiating \\\"spectest\\\"\");"
)?;
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
// as backslash from being reinterpreted.
for c in path.display().to_string().chars() {

View File

@@ -9,7 +9,7 @@ cargo-fuzz = true
[dependencies]
wasmtime-environ = { path = "../lib/environ" }
wasmtime-execute = { path = "../lib/execute" }
wasmtime-jit = { path = "../lib/jit" }
cranelift-codegen = "0.26.0"
cranelift-wasm = "0.26.0"
cranelift-native = "0.26.0"

View File

@@ -6,7 +6,7 @@ extern crate cranelift_codegen;
extern crate cranelift_native;
extern crate wasmparser;
extern crate wasmtime_environ;
extern crate wasmtime_execute;
extern crate wasmtime_jit;
use cranelift_codegen::settings;
use wasmparser::validate;
@@ -28,8 +28,7 @@ fuzz_target!(|data: &[u8]| {
Err(_) => return,
};
let imports_resolver = |_env: &str, _function: &str| None;
let _exec =
match wasmtime_execute::compile_and_link_module(&*isa, &translation, imports_resolver) {
let _exec = match wasmtime_jit::compile_and_link_module(&*isa, &translation, imports_resolver) {
Ok(x) => x,
Err(_) => return,
};

View File

@@ -1,5 +1,6 @@
This is the `wasmtime-environ` crate, which contains the implementations
of the `ModuleEnvironment` and `FuncEnvironment` traits from
[`cranelift-wasm`](https://crates.io/crates/cranelift-wasm). They effectively
implement an ABI for basic wasm compilation, which can be used for JITing,
native object files, or other purposes.
implement an ABI for basic wasm compilation that defines how linear memories
are allocated, how indirect calls work, and other details. They can be used
for JITing, native object files, or other purposes.

View File

@@ -3,16 +3,9 @@
use cranelift_codegen::binemit;
use cranelift_codegen::ir;
use cranelift_codegen::ir::ExternalName;
use cranelift_codegen::isa;
use cranelift_codegen::{CodegenError, Context};
use cranelift_codegen::CodegenError;
use cranelift_entity::PrimaryMap;
use cranelift_wasm::{DefinedFuncIndex, FuncIndex, FuncTranslator, 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 cranelift_wasm::{DefinedFuncIndex, FuncIndex, WasmError};
use std::vec::Vec;
/// 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.
#[derive(Debug, Clone)]
pub struct Relocation {
@@ -127,44 +55,6 @@ pub enum RelocationTarget {
/// Relocations to apply to function bodies.
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.
#[derive(Fail, Debug)]
pub enum CompileError {

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

View File

@@ -7,7 +7,7 @@ use cranelift_codegen::ir::types::*;
use cranelift_codegen::ir::{
AbiParam, ArgumentPurpose, ExtFuncData, FuncRef, Function, InstBuilder, Signature,
};
use cranelift_codegen::isa;
use cranelift_codegen::isa::TargetFrontendConfig;
use cranelift_entity::EntityRef;
use cranelift_wasm::{
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`.
pub struct FuncEnvironment<'module_environment> {
/// Compilation setting flags.
isa: &'module_environment isa::TargetIsa,
/// Target-specified configuration.
target_config: TargetFrontendConfig,
/// The module-level environment which this function-level environment belongs to.
module: &'module_environment Module,
@@ -104,12 +104,9 @@ pub struct FuncEnvironment<'module_environment> {
}
impl<'module_environment> FuncEnvironment<'module_environment> {
pub fn new(
isa: &'module_environment isa::TargetIsa,
module: &'module_environment Module,
) -> Self {
pub fn new(target_config: TargetFrontendConfig, module: &'module_environment Module) -> Self {
Self {
isa,
target_config,
module,
vmctx: None,
imported_functions_base: None,
@@ -124,12 +121,12 @@ impl<'module_environment> FuncEnvironment<'module_environment> {
imported_memory32_size_extfunc: None,
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 {
self.isa.frontend_config().pointer_type()
self.target_config.pointer_type()
}
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),
],
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),
],
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> {
fn target_config(&self) -> isa::TargetFrontendConfig {
self.isa.frontend_config()
fn target_config(&self) -> TargetFrontendConfig {
self.target_config
}
fn make_table(&mut self, func: &mut ir::Function, index: TableIndex) -> ir::Table {

View File

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

View File

@@ -6,7 +6,6 @@ use cranelift_wasm::{
DefinedFuncIndex, DefinedGlobalIndex, DefinedMemoryIndex, DefinedTableIndex, FuncIndex, Global,
GlobalIndex, Memory, MemoryIndex, SignatureIndex, Table, TableIndex,
};
use std::cmp;
use std::collections::HashMap;
use std::string::String;
use std::vec::Vec;

View File

@@ -1,6 +1,6 @@
use cranelift_codegen::ir;
use cranelift_codegen::ir::{AbiParam, ArgumentPurpose};
use cranelift_codegen::isa;
use cranelift_codegen::isa::TargetFrontendConfig;
use cranelift_entity::PrimaryMap;
use cranelift_wasm::{
self, translate_module, DefinedFuncIndex, FuncIndex, Global, GlobalIndex, Memory, MemoryIndex,
@@ -13,139 +13,149 @@ use std::string::String;
use std::vec::Vec;
use tunables::Tunables;
/// Object containing the standalone environment information. To be passed after creation as
/// argument to `compile_module`.
pub struct ModuleEnvironment<'data, 'module> {
/// The result of translating via `ModuleEnvironment`. Function bodies are not
/// yet translated, and data initializers have not yet been copied out of the
/// original buffer.
pub struct ModuleTranslation<'data> {
/// Compilation setting flags.
isa: &'module isa::TargetIsa,
pub target_config: TargetFrontendConfig,
/// Module information.
module: &'module mut Module,
pub module: Module,
/// References to information to be decoded later.
lazy: 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>>,
/// Tunable parameters.
tunables: Tunables,
pub tunables: Tunables,
}
impl<'data, 'module> ModuleEnvironment<'data, 'module> {
/// Allocates the enironment data structures with the given isa.
pub fn new(
isa: &'module isa::TargetIsa,
module: &'module mut Module,
tunables: Tunables,
) -> Self {
impl<'data> ModuleTranslation<'data> {
/// Return a new `FuncEnvironment` for translating a function.
pub fn func_env(&self) -> FuncEnvironment {
FuncEnvironment::new(self.target_config, &self.module)
}
}
/// 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 {
isa,
module,
lazy: LazyContents::new(),
result: ModuleTranslation {
target_config,
module: Module::new(),
function_body_inputs: PrimaryMap::new(),
data_initializers: Vec::new(),
tunables,
},
}
}
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
/// `ModuleEnvironment` with its mutable reference to the `Module` and produces a
/// `ModuleTranslation` with an immutable reference to the `Module` (which has
/// become fully populated).
pub fn translate(mut self, data: &'data [u8]) -> WasmResult<ModuleTranslation<'data, 'module>> {
/// Translate a wasm module using this environment. This consumes the
/// `ModuleEnvironment` and produces a `ModuleTranslation`.
pub fn translate(mut self, data: &'data [u8]) -> WasmResult<ModuleTranslation<'data>> {
translate_module(data, &mut self)?;
Ok(ModuleTranslation {
isa: self.isa,
module: self.module,
lazy: self.lazy,
tunables: self.tunables,
})
Ok(self.result)
}
}
/// 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.
impl<'data, 'module> cranelift_wasm::ModuleEnvironment<'data>
for ModuleEnvironment<'data, 'module>
{
fn target_config(&self) -> isa::TargetFrontendConfig {
self.isa.frontend_config()
impl<'data> cranelift_wasm::ModuleEnvironment<'data> for ModuleEnvironment<'data> {
fn target_config(&self) -> TargetFrontendConfig {
self.result.target_config
}
fn declare_signature(&mut self, sig: &ir::Signature) {
let sig = translate_signature(sig.clone(), self.pointer_type());
// TODO: Deduplicate signatures.
self.module.signatures.push(sig);
self.result.module.signatures.push(sig);
}
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) {
debug_assert_eq!(
self.module.functions.len(),
self.module.imported_funcs.len(),
self.result.module.functions.len(),
self.result.module.imported_funcs.len(),
"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
.push((String::from(module), String::from(field)));
}
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) {
self.module.functions.push(sig_index);
self.result.module.functions.push(sig_index);
}
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) {
debug_assert_eq!(
self.module.globals.len(),
self.module.imported_globals.len(),
self.result.module.globals.len(),
self.result.module.imported_globals.len(),
"Imported globals must be declared first"
);
self.module.globals.push(global);
self.result.module.globals.push(global);
self.module
self.result
.module
.imported_globals
.push((String::from(module), String::from(field)));
}
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 {
&self.module.globals[global_index]
&self.result.module.globals[global_index]
}
fn declare_table_import(&mut self, table: Table, module: &str, field: &str) {
debug_assert_eq!(
self.module.table_plans.len(),
self.module.imported_tables.len(),
self.result.module.table_plans.len(),
self.result.module.imported_tables.len(),
"Imported tables must be declared first"
);
let plan = TablePlan::for_table(table, &self.tunables);
self.module.table_plans.push(plan);
let plan = TablePlan::for_table(table, &self.result.tunables);
self.result.module.table_plans.push(plan);
self.module
self.result
.module
.imported_tables
.push((String::from(module), String::from(field)));
}
fn declare_table(&mut self, table: Table) {
let plan = TablePlan::for_table(table, &self.tunables);
self.module.table_plans.push(plan);
let plan = TablePlan::for_table(table, &self.result.tunables);
self.result.module.table_plans.push(plan);
}
fn declare_table_elements(
@@ -155,7 +165,7 @@ impl<'data, 'module> cranelift_wasm::ModuleEnvironment<'data>
offset: usize,
elements: Vec<FuncIndex>,
) {
self.module.table_elements.push(TableElements {
self.result.module.table_elements.push(TableElements {
table_index,
base,
offset,
@@ -165,21 +175,22 @@ impl<'data, 'module> cranelift_wasm::ModuleEnvironment<'data>
fn declare_memory_import(&mut self, memory: Memory, module: &str, field: &str) {
debug_assert_eq!(
self.module.memory_plans.len(),
self.module.imported_memories.len(),
self.result.module.memory_plans.len(),
self.result.module.imported_memories.len(),
"Imported memories must be declared first"
);
let plan = MemoryPlan::for_memory(memory, &self.tunables);
self.module.memory_plans.push(plan);
let plan = MemoryPlan::for_memory(memory, &self.result.tunables);
self.result.module.memory_plans.push(plan);
self.module
self.result
.module
.imported_memories
.push((String::from(module), String::from(field)));
}
fn declare_memory(&mut self, memory: Memory) {
let plan = MemoryPlan::for_memory(memory, &self.tunables);
self.module.memory_plans.push(plan);
let plan = MemoryPlan::for_memory(memory, &self.result.tunables);
self.result.module.memory_plans.push(plan);
}
fn declare_data_initialization(
@@ -189,71 +200,55 @@ impl<'data, 'module> cranelift_wasm::ModuleEnvironment<'data>
offset: usize,
data: &'data [u8],
) {
self.lazy.data_initializers.push(DataInitializer {
self.result.data_initializers.push(DataInitializer {
location: DataInitializerLocation {
memory_index,
base,
offset,
},
data,
});
}
fn declare_func_export(&mut self, func_index: FuncIndex, name: &str) {
self.module
self.result
.module
.exports
.insert(String::from(name), Export::Function(func_index));
}
fn declare_table_export(&mut self, table_index: TableIndex, name: &str) {
self.module
self.result
.module
.exports
.insert(String::from(name), Export::Table(table_index));
}
fn declare_memory_export(&mut self, memory_index: MemoryIndex, name: &str) {
self.module
self.result
.module
.exports
.insert(String::from(name), Export::Memory(memory_index));
}
fn declare_global_export(&mut self, global_index: GlobalIndex, name: &str) {
self.module
self.result
.module
.exports
.insert(String::from(name), Export::Global(global_index));
}
fn declare_start_func(&mut self, func_index: FuncIndex) {
debug_assert!(self.module.start_func.is_none());
self.module.start_func = Some(func_index);
debug_assert!(self.result.module.start_func.is_none());
self.result.module.start_func = Some(func_index);
}
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(())
}
}
/// 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.
pub fn translate_signature(mut sig: ir::Signature, pointer_type: ir::Type) -> ir::Signature {
sig.params
@@ -261,33 +256,25 @@ pub fn translate_signature(mut sig: ir::Signature, pointer_type: ir::Type) -> ir
sig
}
/// A data initializer for linear memory.
pub struct DataInitializer<'data> {
/// A memory index and offset within that memory where a data initialization
/// should is to be performed.
#[derive(Clone)]
pub struct DataInitializerLocation {
/// The index of the memory to initialize.
pub memory_index: MemoryIndex,
/// Optionally a globalvar base to initialize at.
pub base: Option<GlobalIndex>,
/// A constant offset to initialize at.
pub offset: usize,
}
/// 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.
pub data: &'data [u8],
}
/// References to the input wasm data buffer to be decoded and processed later,
/// separately from the main module translation.
pub struct LazyContents<'data> {
/// References to the function bodies.
pub function_body_inputs: PrimaryMap<DefinedFuncIndex, &'data [u8]>,
/// References to the data initializers.
pub data_initializers: Vec<DataInitializer<'data>>,
}
impl<'data> LazyContents<'data> {
pub fn new() -> Self {
Self {
function_body_inputs: PrimaryMap::new(),
data_initializers: Vec::new(),
}
}
}

View File

@@ -1,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.
use cranelift_codegen::ir;

View File

@@ -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

View File

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

View File

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

View File

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

View File

@@ -1,5 +1,5 @@
[package]
name = "wasmtime-execute"
name = "wasmtime-jit"
version = "0.1.0"
authors = ["The Cranelift Project Developers"]
publish = false

6
lib/jit/README.md Normal file
View 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
View 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
)))
}
})
}
}

View File

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

252
lib/jit/src/compiler.rs Normal file
View 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
View 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)
}

View File

@@ -36,27 +36,31 @@ extern crate wasmtime_runtime;
#[macro_use]
extern crate alloc;
extern crate failure;
extern crate target_lexicon;
#[macro_use]
extern crate failure_derive;
extern crate target_lexicon;
mod action;
mod instance_plus;
mod jit_code;
mod code_memory;
mod compiler;
mod instantiate;
mod link;
mod namespace;
mod resolver;
mod target_tunables;
mod trampoline_park;
pub use action::{ActionError, ActionOutcome, RuntimeValue};
pub use instance_plus::InstancePlus;
pub use jit_code::JITCode;
pub use compiler::Compiler;
pub use instantiate::{instantiate, CompiledModule, SetupError};
pub use link::link_module;
pub use namespace::{InstancePlusIndex, Namespace};
pub use namespace::{InstanceIndex, Namespace};
pub use resolver::{NullResolver, Resolver};
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"))]
mod std {
pub use alloc::{boxed, rc, string, vec};

View File

@@ -1,24 +1,20 @@
//! Linking for JIT-compiled code.
use cranelift_codegen::binemit::Reloc;
use cranelift_entity::PrimaryMap;
use cranelift_wasm::{DefinedFuncIndex, Global, GlobalInit, Memory, Table, TableElementType};
use resolver::Resolver;
use std::ptr::write_unaligned;
use std::string::String;
use std::vec::Vec;
use wasmtime_environ::{
MemoryPlan, MemoryStyle, Module, Relocation, RelocationTarget, Relocations, TablePlan,
};
use wasmtime_runtime::libcalls;
use wasmtime_runtime::{
Export, Imports, VMFunctionBody, VMFunctionImport, VMGlobalImport, VMMemoryImport,
Export, Imports, LinkError, VMFunctionBody, VMFunctionImport, VMGlobalImport, VMMemoryImport,
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`.
pub fn link_module(
module: &Module,

View File

@@ -2,19 +2,20 @@
//! to exports. This file provides one possible way to manage multiple instances
//! and resolve imports to exports among them.
use action::{get, inspect_memory, invoke};
use action::{ActionError, ActionOutcome, RuntimeValue};
use cranelift_codegen::isa;
use compiler::Compiler;
use cranelift_entity::PrimaryMap;
use instance_plus::InstancePlus;
use jit_code::JITCode;
use resolver::Resolver;
use std::boxed::Box;
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)]
pub struct InstancePlusIndex(u32);
entity_impl!(InstancePlusIndex, "instance");
pub struct InstanceIndex(u32);
entity_impl!(InstanceIndex, "instance");
/// A namespace containing instances keyed by name.
///
@@ -22,10 +23,10 @@ entity_impl!(InstancePlusIndex, "instance");
/// imports using defined exports.
pub struct Namespace {
/// Mapping from identifiers to indices in `self.instances`.
names: HashMap<String, InstancePlusIndex>,
names: HashMap<String, InstanceIndex>,
/// The instances, available by index.
instances: PrimaryMap<InstancePlusIndex, InstancePlus>,
instances: PrimaryMap<InstanceIndex, Box<Instance>>,
}
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.
pub fn instance(
&mut self,
instance_name: Option<&str>,
instance: InstancePlus,
) -> InstancePlusIndex {
instance: Box<Instance>,
) -> InstanceIndex {
let index = self.instances.push(instance);
if let Some(instance_name) = instance_name {
self.names.insert(instance_name.into(), index);
@@ -52,41 +53,47 @@ impl Namespace {
}
/// 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)
}
/// 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);
}
/// Invoke an exported function from an instance.
pub fn invoke(
&mut self,
jit_code: &mut JITCode,
isa: &isa::TargetIsa,
index: InstancePlusIndex,
compiler: &mut Compiler,
index: InstanceIndex,
field_name: &str,
args: &[RuntimeValue],
) -> 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.
pub fn get(
&mut self,
index: InstancePlusIndex,
field_name: &str,
) -> Result<RuntimeValue, ActionError> {
self.instances[index].get(&field_name)
pub fn get(&self, index: InstanceIndex, field_name: &str) -> Result<RuntimeValue, ActionError> {
get(&self.instances[index], &field_name)
}
}
impl Resolver for Namespace {
fn resolve(&mut self, instance: &str, field: &str) -> Option<Export> {
if let Some(index) = self.names.get(instance) {
self.instances[*index].instance.lookup(field)
self.instances[*index].lookup(field)
} else {
None
}

View File

@@ -1,3 +1,6 @@
//! Define the `Resolver` trait, allowing custom resolution for external
//! references.
use wasmtime_runtime::Export;
/// Import resolver connects imports with available exported values.

View File

@@ -1,7 +1,7 @@
This is the `wasmtime-runtime` crate, which contains wasm runtime library
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-execute`]: https://crates.io/crates/wasmtime-execute
[`wasmtime-jit`]: https://crates.io/crates/wasmtime-jit
[`wasmtime-obj`]: https://crates.io/crates/wasmtime-obj

View File

@@ -640,9 +640,9 @@ WasmTrapHandler(int signum, siginfo_t* info, void* context)
}
assert(previousSignal);
// This signal is not for any JIT code we expect, so we need to forward
// the signal to the next handler. If there is no next handler (SIG_IGN or
// SIG_DFL), then it's time to crash. To do this, we set the signal back to
// This signal is not for any compiled wasm code we expect, so we need to
// forward the signal to the next handler. If there is no next handler (SIG_IGN
// 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
// 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

View File

@@ -3,7 +3,7 @@ use cranelift_wasm::{FuncIndex, GlobalIndex, MemoryIndex, TableIndex};
use vmcontext::{VMFunctionImport, VMGlobalImport, VMMemoryImport, VMTableImport};
/// Resolved import pointers.
#[derive(Debug)]
#[derive(Debug, Clone)]
pub struct Imports {
/// Resolved addresses for imported functions.
pub functions: BoxedSlice<FuncIndex, VMFunctionImport>,

View File

@@ -23,6 +23,9 @@ use vmcontext::{
use wasmtime_environ::{DataInitializer, 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)]
pub struct Instance {
/// The `Module` this `Instance` was instantiated from.
@@ -53,7 +56,7 @@ pub struct Instance {
/// WebAssembly global variable data.
vmctx_globals: BoxedSlice<DefinedGlobalIndex, VMGlobalDefinition>,
/// Context pointer used by JIT code.
/// Context pointer used by compiled wasm code.
vmctx: VMContext,
}
@@ -63,7 +66,7 @@ impl Instance {
module: Rc<Module>,
finished_functions: BoxedSlice<DefinedFuncIndex, *const VMFunctionBody>,
mut vmctx_imports: Imports,
data_initializers: Vec<DataInitializer>,
data_initializers: &[DataInitializer],
) -> Result<Box<Self>, InstantiationError> {
let mut sig_registry = create_and_initialize_signatures(&module);
let mut tables = create_tables(&module);
@@ -125,7 +128,7 @@ impl Instance {
// Check initializer bounds before initializing anything.
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.
initialize_tables(&mut *result)?;
@@ -148,22 +151,22 @@ impl Instance {
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 {
&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 {
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 {
&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 {
self.vmctx_mut()
}
@@ -184,7 +187,7 @@ impl Instance {
.unwrap_or_else(|| panic!("no memory for index {}", memory_index.index()))
.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();
result
@@ -314,7 +317,7 @@ impl Instance {
/// This requirement is not enforced in the type system, so this function is
/// unsafe.
pub unsafe fn lookup_immutable(&self, field: &str) -> Option<Export> {
let temporary_mut = &mut *(self as *const Instance as *mut Instance);
let temporary_mut = &mut *(self as *const Self as *mut Self);
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() {
return Err(InstantiationError::Link(
return Err(InstantiationError::Link(LinkError(
"elements segment does not fit".to_owned(),
));
)));
}
}
@@ -361,8 +364,8 @@ fn check_memory_init_bounds(
) -> Result<(), InstantiationError> {
for init in data_initializers {
// TODO: Refactor this.
let mut start = init.offset;
if let Some(base) = init.base {
let mut start = init.location.offset;
if let Some(base) = init.location.base {
let global = if let Some(def_index) = instance.module.defined_global_index(base) {
unsafe { instance.vmctx.global_mut(def_index) }
} else {
@@ -372,12 +375,13 @@ fn check_memory_init_bounds(
}
// TODO: Refactor this.
let memory = if let Some(defined_memory_index) =
instance.module.defined_memory_index(init.memory_index)
let memory = if let Some(defined_memory_index) = instance
.module
.defined_memory_index(init.location.memory_index)
{
unsafe { instance.vmctx.memory(defined_memory_index) }
} 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_memory = unsafe { &mut *(import).from };
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) };
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(),
));
)));
}
}
@@ -457,9 +461,9 @@ fn initialize_tables(instance: &mut Instance) -> Result<(), InstantiationError>
};
}
} else {
return Err(InstantiationError::Link(
return Err(InstantiationError::Link(LinkError(
"elements segment does not fit".to_owned(),
));
)));
}
}
@@ -482,11 +486,11 @@ fn create_memories(
/// Initialize the table memory from the provided initializers.
fn initialize_memories(
instance: &mut Instance,
data_initializers: Vec<DataInitializer>,
data_initializers: &[DataInitializer],
) -> Result<(), InstantiationError> {
for init in data_initializers {
let mut start = init.offset;
if let Some(base) = init.base {
let mut start = init.location.offset;
if let Some(base) = init.location.base {
let global = if let Some(def_index) = instance.module.defined_global_index(base) {
unsafe { instance.vmctx.global_mut(def_index) }
} else {
@@ -495,12 +499,13 @@ fn initialize_memories(
start += unsafe { *(&*global).as_i32() } as u32 as usize;
}
let memory = if let Some(defined_memory_index) =
instance.module.defined_memory_index(init.memory_index)
let memory = if let Some(defined_memory_index) = instance
.module
.defined_memory_index(init.location.memory_index)
{
unsafe { instance.vmctx.memory(defined_memory_index) }
} 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_memory = unsafe { &mut *(import).from };
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()) {
to_init.copy_from_slice(init.data);
} else {
return Err(InstantiationError::Link(
return Err(InstantiationError::Link(LinkError(
"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.
#[derive(Fail, Debug)]
pub enum InstantiationError {
@@ -562,9 +572,9 @@ pub enum InstantiationError {
#[fail(display = "Insufficient resources: {}", _0)]
Resource(String),
/// A wasm translation error occured.
#[fail(display = "Link error: {}", _0)]
Link(String),
/// A wasm link error occured.
#[fail(display = "{}", _0)]
Link(LinkError),
/// A compilation error occured.
#[fail(display = "Trap occurred while invoking start function: {}", _0)]

View File

@@ -60,7 +60,7 @@ pub mod libcalls;
pub use export::Export;
pub use imports::Imports;
pub use instance::{Instance, InstantiationError};
pub use instance::{Instance, InstantiationError, LinkError};
pub use mmap::Mmap;
pub use signalhandlers::{wasmtime_init_eager, wasmtime_init_finish};
pub use traphandlers::{wasmtime_call, wasmtime_call_trampoline};

View File

@@ -1,6 +1,6 @@
//! Runtime library calls. Note that the JIT may sometimes perform these inline
//! rather than calling them, particularly when CPUs have special instructions
//! which compute them directly.
//! Runtime library calls. Note that wasm compilers may sometimes perform these
//! inline rather than calling them, particularly when CPUs have special
//! instructions which compute them directly.
use cranelift_wasm::{DefinedMemoryIndex, MemoryIndex};
use vmcontext::VMContext;

View File

@@ -148,7 +148,7 @@ impl LinearMemory {
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 {
VMMemoryDefinition {
base: self.mmap.as_mut_ptr(),

View File

@@ -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 {
VMTableDefinition {
base: self.vec.as_mut_ptr() as *mut u8,

View File

@@ -1,5 +1,5 @@
//! 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_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.
#[derive(Debug, Copy, Clone)]
#[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.
#[derive(Debug, Copy, Clone)]
#[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.
#[derive(Debug, Copy, Clone)]
#[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
/// size in bytes.
#[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.
#[derive(Debug, Copy, Clone)]
#[repr(C)]

View File

@@ -14,7 +14,7 @@ cranelift-codegen = "0.26.0"
cranelift-native = "0.26.0"
cranelift-wasm = "0.26.0"
cranelift-entity = "0.26.0"
wasmtime-execute = { path = "../execute" }
wasmtime-jit = { path = "../jit" }
wasmtime-runtime = { path = "../runtime" }
wasmtime-environ = { path = "../environ" }
wabt = "0.7"

View File

@@ -32,7 +32,7 @@ extern crate target_lexicon;
extern crate wabt;
extern crate wasmparser;
extern crate wasmtime_environ;
extern crate wasmtime_execute;
extern crate wasmtime_jit;
extern crate wasmtime_runtime;
mod spectest;

View File

@@ -2,13 +2,12 @@ use cranelift_codegen::ir::types;
use cranelift_codegen::{ir, isa};
use cranelift_entity::PrimaryMap;
use cranelift_wasm::{DefinedFuncIndex, Global, GlobalInit, Memory, Table, TableElementType};
use std::rc::Rc;
use target_lexicon::HOST;
use wasmtime_environ::{
translate_signature, Export, MemoryPlan, MemoryStyle, Module, TablePlan, TableStyle,
};
use wasmtime_execute::{target_tunables, ActionError, InstancePlus};
use wasmtime_runtime::{Imports, VMFunctionBody};
use wasmtime_jit::{target_tunables, CompiledModule};
use wasmtime_runtime::{Imports, Instance, InstantiationError, VMFunctionBody};
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
/// 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 pointer_type = types::Type::triple_pointer_type(&HOST);
let mut module = Module::new();
@@ -218,10 +217,11 @@ pub fn instantiate_spectest() -> Result<InstancePlus, ActionError> {
let imports = Imports::none();
let data_initializers = Vec::new();
InstancePlus::with_parts(
Rc::new(module),
CompiledModule::from_parts(
module,
finished_functions.into_boxed_slice(),
imports,
data_initializers,
data_initializers.into_boxed_slice(),
)
.instantiate()
}

View File

@@ -1,12 +1,12 @@
use cranelift_codegen::isa;
use spectest::instantiate_spectest;
use std::io::Read;
use std::path::Path;
use std::{fmt, fs, io, str};
use wabt::script::{Action, Command, CommandKind, ModuleBinary, ScriptParser, Value};
use wasmparser::{validate, OperatorValidatorConfig, ValidatingParserConfig};
use wasmtime_execute::{
ActionError, ActionOutcome, InstancePlus, InstancePlusIndex, JITCode, Namespace, RuntimeValue,
use wasmtime_jit::{
instantiate, ActionError, ActionOutcome, Compiler, Instance, InstanceIndex, InstantiationError,
Namespace, RuntimeValue, SetupError,
};
/// Translate from a script::Value to a RuntimeValue.
@@ -77,22 +77,22 @@ pub struct WastFileError {
/// to be performed on them.
pub struct WastContext {
/// A namespace of wasm modules, keyed by an optional name.
current: Option<InstancePlusIndex>,
current: Option<InstanceIndex>,
namespace: Namespace,
jit_code: JITCode,
compiler: Box<Compiler>,
}
impl WastContext {
/// Construct a new instance of `WastContext`.
pub fn new() -> Self {
pub fn new(compiler: Box<Compiler>) -> Self {
Self {
current: None,
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 {
operator_config: OperatorValidatorConfig {
enable_threads: false,
@@ -107,26 +107,19 @@ impl WastContext {
Ok(())
} else {
// 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(
&mut self,
isa: &isa::TargetIsa,
module: ModuleBinary,
) -> Result<InstancePlus, ActionError> {
fn instantiate(&mut self, module: ModuleBinary) -> Result<Box<Instance>, SetupError> {
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(
&mut self,
instance_name: &Option<String>,
) -> Result<InstancePlusIndex, WastError> {
fn get_index(&mut self, instance_name: &Option<String>) -> Result<InstanceIndex, WastError> {
let index = *if let Some(instance_name) = instance_name {
self.namespace
.get_instance_index(instance_name)
@@ -145,24 +138,20 @@ impl WastContext {
}
/// 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()?;
self.namespace.instance(Some("spectest"), instance);
Ok(())
}
/// Perform the action portion of a command.
fn perform_action(
&mut self,
isa: &isa::TargetIsa,
action: Action,
) -> Result<ActionOutcome, WastError> {
fn perform_action(&mut self, action: Action) -> Result<ActionOutcome, WastError> {
match action {
Action::Invoke {
module: instance_name,
field,
args,
} => self.invoke(isa, instance_name, &field, &args),
} => self.invoke(instance_name, &field, &args),
Action::Get {
module: instance_name,
field,
@@ -173,11 +162,10 @@ impl WastContext {
/// Define a module and register it.
fn module(
&mut self,
isa: &isa::TargetIsa,
instance_name: Option<String>,
module: ModuleBinary,
) -> Result<(), ActionError> {
let instance = self.instantiate(isa, module)?;
let instance = self.instantiate(module).map_err(ActionError::Setup)?;
let index = self
.namespace
.instance(instance_name.as_ref().map(String::as_str), instance);
@@ -195,7 +183,6 @@ impl WastContext {
/// Invoke an exported function from an instance.
fn invoke(
&mut self,
isa: &isa::TargetIsa,
instance_name: Option<String>,
field: &str,
args: &[Value],
@@ -206,7 +193,7 @@ impl WastContext {
.collect::<Vec<_>>();
let index = self.get_index(&instance_name)?;
self.namespace
.invoke(&mut self.jit_code, isa, index, &field, &value_args)
.invoke(&mut *self.compiler, index, &field, &value_args)
.map_err(WastError::Action)
}
@@ -227,24 +214,15 @@ impl WastContext {
}
/// Perform the action of a `PerformAction`.
fn perform_action_command(
&mut self,
isa: &isa::TargetIsa,
action: Action,
) -> Result<(), WastError> {
match self.perform_action(isa, action)? {
fn perform_action_command(&mut self, action: Action) -> Result<(), WastError> {
match self.perform_action(action)? {
ActionOutcome::Returned { .. } => Ok(()),
ActionOutcome::Trapped { message } => Err(WastError::Trap(message)),
}
}
/// Run a wast script from a byte buffer.
pub fn run_buffer(
&mut self,
isa: &isa::TargetIsa,
filename: &str,
wast: &[u8],
) -> Result<(), WastFileError> {
pub fn run_buffer(&mut self, filename: &str, wast: &[u8]) -> Result<(), WastFileError> {
let mut parser = ScriptParser::from_str(str::from_utf8(wast).unwrap()).unwrap();
while let Some(Command { kind, line }) = parser.next().expect("parser") {
@@ -253,7 +231,7 @@ impl WastContext {
module: instance_name,
name,
} => {
self.module(isa, name, instance_name)
self.module(name, instance_name)
.map_err(|error| WastFileError {
filename: filename.to_string(),
line,
@@ -269,7 +247,7 @@ impl WastContext {
})?;
}
CommandKind::PerformAction(action) => {
self.perform_action_command(isa, action)
self.perform_action_command(action)
.map_err(|error| WastFileError {
filename: filename.to_string(),
line,
@@ -277,9 +255,7 @@ impl WastContext {
})?;
}
CommandKind::AssertReturn { action, expected } => {
match self
.perform_action(isa, action)
.map_err(|error| WastFileError {
match self.perform_action(action).map_err(|error| WastFileError {
filename: filename.to_string(),
line,
error,
@@ -312,9 +288,7 @@ impl WastContext {
}
}
CommandKind::AssertTrap { action, message } => {
match self
.perform_action(isa, action)
.map_err(|error| WastFileError {
match self.perform_action(action).map_err(|error| WastFileError {
filename: filename.to_string(),
line,
error,
@@ -340,9 +314,7 @@ impl WastContext {
}
}
CommandKind::AssertExhaustion { action } => {
match self
.perform_action(isa, action)
.map_err(|error| WastFileError {
match self.perform_action(action).map_err(|error| WastFileError {
filename: filename.to_string(),
line,
error,
@@ -366,9 +338,7 @@ impl WastContext {
}
}
CommandKind::AssertReturnCanonicalNan { action } => {
match self
.perform_action(isa, action)
.map_err(|error| WastFileError {
match self.perform_action(action).map_err(|error| WastFileError {
filename: filename.to_string(),
line,
error,
@@ -420,9 +390,7 @@ impl WastContext {
}
}
CommandKind::AssertReturnArithmeticNan { action } => {
match self
.perform_action(isa, action)
.map_err(|error| WastFileError {
match self.perform_action(action).map_err(|error| WastFileError {
filename: filename.to_string(),
line,
error,
@@ -474,7 +442,7 @@ impl WastContext {
}
}
CommandKind::AssertInvalid { module, message } => {
self.module(isa, None, module).expect_err(&format!(
self.module(None, module).expect_err(&format!(
"{}:{}: invalid module was successfully instantiated",
filename, line
));
@@ -484,7 +452,7 @@ impl WastContext {
);
}
CommandKind::AssertMalformed { module, message } => {
self.module(isa, None, module).expect_err(&format!(
self.module(None, module).expect_err(&format!(
"{}:{}: malformed module was successfully instantiated",
filename, line
));
@@ -494,7 +462,7 @@ impl WastContext {
);
}
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",
filename, line
));
@@ -504,7 +472,7 @@ impl WastContext {
);
}
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",
filename, line
));
@@ -520,14 +488,14 @@ impl WastContext {
}
/// 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 buffer = read_to_end(path).map_err(|e| WastFileError {
filename,
line: 0,
error: WastError::IO(e),
})?;
self.run_buffer(isa, &path.display().to_string(), &buffer)
self.run_buffer(&path.display().to_string(), &buffer)
}
}

View File

@@ -53,7 +53,7 @@ use std::path::PathBuf;
use std::process;
use std::str::FromStr;
use target_lexicon::Triple;
use wasmtime_environ::{compile_module, Module, ModuleEnvironment, Tunables};
use wasmtime_environ::{cranelift, ModuleEnvironment, Tunables};
use wasmtime_obj::emit_module;
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 module = Module::new();
// TODO: Expose the tunables as command-line flags.
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
// 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))
.map_err(|err| format!("{}", err))?;
}
let (compilation, relocations) = compile_module(
&translation.module,
&translation.lazy.function_body_inputs,
&*isa,
)
let (compilation, relocations) =
cranelift::compile_module(&module, lazy_function_body_inputs, &*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 translation.module.table_plans.len() > 1 {
if !module.table_plans.is_empty() {
if module.table_plans.len() > 1 {
return Err(String::from("multiple tables not supported yet"));
}
return Err(String::from("FIXME: implement tables"));

View File

@@ -33,7 +33,7 @@
extern crate cranelift_codegen;
extern crate cranelift_native;
extern crate docopt;
extern crate wasmtime_execute;
extern crate wasmtime_jit;
extern crate wasmtime_wast;
#[macro_use]
extern crate serde_derive;
@@ -41,7 +41,6 @@ extern crate file_per_thread_logger;
extern crate pretty_env_logger;
extern crate wabt;
use cranelift_codegen::isa::TargetIsa;
use cranelift_codegen::settings;
use cranelift_codegen::settings::Configurable;
use docopt::Docopt;
@@ -52,7 +51,7 @@ use std::io::prelude::*;
use std::path::Path;
use std::path::PathBuf;
use std::process::exit;
use wasmtime_execute::{ActionOutcome, InstancePlus, JITCode, Namespace};
use wasmtime_jit::{instantiate, ActionOutcome, Compiler, Namespace};
use wasmtime_wast::instantiate_spectest;
static LOG_FILENAME_PREFIX: &str = "wasmtime.dbg.";
@@ -123,6 +122,7 @@ fn main() {
}
let isa = isa_builder.finish(settings::Flags::new(flag_builder));
let mut compiler = Compiler::new(isa);
let mut namespace = Namespace::new();
@@ -132,11 +132,9 @@ fn main() {
instantiate_spectest().expect("instantiating spectest"),
);
let mut jit_code = JITCode::new();
for filename in &args.arg_file {
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(()) => {}
Err(message) => {
let name = path.as_os_str().to_string_lossy();
@@ -148,11 +146,10 @@ fn main() {
}
fn handle_module(
jit_code: &mut JITCode,
compiler: &mut Compiler,
namespace: &mut Namespace,
args: &Args,
path: &Path,
isa: &TargetIsa,
) -> Result<(), String> {
let mut data =
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()))?;
}
// Create a new `InstancePlus` by compiling and instantiating a wasm module.
let instance_plus =
InstancePlus::new(jit_code, isa, &data, namespace).map_err(|e| e.to_string())?;
// Create a new `Instance` by compiling and instantiating a wasm module.
let instance = instantiate(compiler, &data, namespace).map_err(|e| e.to_string())?;
// 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 let Some(ref f) = args.flag_invoke {
match namespace
.invoke(jit_code, isa, index, &f, &[])
.invoke(compiler, index, &f, &[])
.map_err(|e| e.to_string())?
{
ActionOutcome::Returned { .. } => {}
@@ -191,7 +187,7 @@ mod tests {
use cranelift_codegen::settings::Configurable;
use std::path::PathBuf;
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";
@@ -214,8 +210,8 @@ mod tests {
let isa = isa_builder.finish(settings::Flags::new(flag_builder));
let mut resolver = NullResolver {};
let mut code = JITCode::new();
let instance = InstancePlus::new(&mut code, &*isa, &data, &mut resolver);
let mut compiler = Compiler::new(isa);
let instance = instantiate(&mut compiler, &data, &mut resolver);
assert!(instance.is_ok());
}
}

View File

@@ -28,6 +28,7 @@
extern crate cranelift_codegen;
extern crate cranelift_native;
extern crate docopt;
extern crate wasmtime_jit;
extern crate wasmtime_wast;
#[macro_use]
extern crate serde_derive;
@@ -38,6 +39,7 @@ use cranelift_codegen::settings;
use cranelift_codegen::settings::Configurable;
use docopt::Docopt;
use std::path::Path;
use wasmtime_jit::Compiler;
use wasmtime_wast::WastContext;
static LOG_FILENAME_PREFIX: &str = "cranelift.dbg.";
@@ -94,7 +96,8 @@ fn main() {
}
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
.register_spectest()
@@ -102,7 +105,7 @@ fn main() {
for filename in &args.arg_file {
wast_context
.run_file(&*isa, Path::new(&filename))
.run_file(Path::new(&filename))
.unwrap_or_else(|e| panic!("{}", e));
}
}

View File

@@ -1,11 +1,13 @@
extern crate cranelift_codegen;
extern crate cranelift_native;
extern crate wasmtime_jit;
extern crate wasmtime_wast;
use cranelift_codegen::isa;
use cranelift_codegen::settings;
use cranelift_codegen::settings::Configurable;
use std::path::Path;
use wasmtime_jit::Compiler;
use wasmtime_wast::WastContext;
include!(concat!(env!("OUT_DIR"), "/wast_testsuite_tests.rs"));