Implement the remaining valid spec tests.
And lots of other miscellaneous changes. Rename InstanceWorld to InstancePlus and reorganize its contents. This still isn't a great name, but at least now it has a clear purpose.
This commit is contained in:
2
.gitmodules
vendored
2
.gitmodules
vendored
@@ -1,3 +1,3 @@
|
|||||||
[submodule "lib/wast/spec_testsuite"]
|
[submodule "lib/wast/spec_testsuite"]
|
||||||
path = lib/wast/spec_testsuite
|
path = spec_testsuite
|
||||||
url = https://github.com/WebAssembly/testsuite
|
url = https://github.com/WebAssembly/testsuite
|
||||||
|
|||||||
@@ -14,8 +14,8 @@ name = "wasmtime"
|
|||||||
path = "src/wasmtime.rs"
|
path = "src/wasmtime.rs"
|
||||||
|
|
||||||
[[bin]]
|
[[bin]]
|
||||||
name = "run_wast"
|
name = "wast"
|
||||||
path = "src/run_wast.rs"
|
path = "src/wast.rs"
|
||||||
|
|
||||||
[[bin]]
|
[[bin]]
|
||||||
name = "wasm2obj"
|
name = "wasm2obj"
|
||||||
|
|||||||
116
build.rs
Normal file
116
build.rs
Normal file
@@ -0,0 +1,116 @@
|
|||||||
|
//! Build program to generate a program which runs all the testsuites.
|
||||||
|
//!
|
||||||
|
//! By generating a separate `#[test]` test for each file, we allow cargo test
|
||||||
|
//! to automatically run the files in parallel.
|
||||||
|
|
||||||
|
use std::env;
|
||||||
|
use std::fs::{read_dir, DirEntry, File};
|
||||||
|
use std::io::{self, Write};
|
||||||
|
use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let out_dir =
|
||||||
|
PathBuf::from(env::var("OUT_DIR").expect("The OUT_DIR environment variable must be set"));
|
||||||
|
let mut out = File::create(out_dir.join("wast_testsuite_tests.rs"))
|
||||||
|
.expect("error generating test source file");
|
||||||
|
|
||||||
|
test_directory(&mut out, "misc_testsuite").expect("generating tests");
|
||||||
|
test_directory(&mut out, "spec_testsuite").expect("generating tests");
|
||||||
|
}
|
||||||
|
|
||||||
|
fn test_directory(out: &mut File, testsuite: &str) -> io::Result<()> {
|
||||||
|
let mut dir_entries: Vec<_> = read_dir(testsuite)
|
||||||
|
.expect("reading testsuite directory")
|
||||||
|
.map(|r| r.expect("reading testsuite directory entry"))
|
||||||
|
.filter(|dir_entry| {
|
||||||
|
let p = dir_entry.path();
|
||||||
|
if let Some(ext) = p.extension() {
|
||||||
|
// Only look at wast files.
|
||||||
|
if ext == "wast" {
|
||||||
|
// Ignore files starting with `.`, which could be editor temporary files
|
||||||
|
if let Some(stem) = p.file_stem() {
|
||||||
|
if let Some(stemstr) = stem.to_str() {
|
||||||
|
if !stemstr.starts_with('.') {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
false
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
dir_entries.sort_by_key(|dir| dir.path());
|
||||||
|
|
||||||
|
writeln!(
|
||||||
|
out,
|
||||||
|
"mod {} {{",
|
||||||
|
Path::new(testsuite)
|
||||||
|
.file_stem()
|
||||||
|
.expect("testsuite filename should have a stem")
|
||||||
|
.to_str()
|
||||||
|
.expect("testsuite filename should be representable as a string")
|
||||||
|
.replace("-", "_")
|
||||||
|
)?;
|
||||||
|
writeln!(out, " use super::{{native_isa, Path, WastContext}};")?;
|
||||||
|
for dir_entry in dir_entries {
|
||||||
|
write_testsuite_tests(out, dir_entry, testsuite)?;
|
||||||
|
}
|
||||||
|
writeln!(out, "}}")?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write_testsuite_tests(out: &mut File, dir_entry: DirEntry, testsuite: &str) -> io::Result<()> {
|
||||||
|
let path = dir_entry.path();
|
||||||
|
let stemstr = path
|
||||||
|
.file_stem()
|
||||||
|
.expect("file_stem")
|
||||||
|
.to_str()
|
||||||
|
.expect("to_str");
|
||||||
|
|
||||||
|
writeln!(out, " #[test]")?;
|
||||||
|
if ignore(testsuite, stemstr) {
|
||||||
|
writeln!(out, " #[ignore]")?;
|
||||||
|
}
|
||||||
|
writeln!(
|
||||||
|
out,
|
||||||
|
" fn {}() {{",
|
||||||
|
avoid_keywords(&stemstr.replace("-", "_"))
|
||||||
|
)?;
|
||||||
|
writeln!(out, " let mut wast_context = WastContext::new();")?;
|
||||||
|
writeln!(out, " let isa = native_isa();")?;
|
||||||
|
writeln!(out, " wast_context")?;
|
||||||
|
writeln!(out, " .register_spectest()")?;
|
||||||
|
writeln!(
|
||||||
|
out,
|
||||||
|
" .expect(\"instantiating \\\"spectest\\\"\");"
|
||||||
|
)?;
|
||||||
|
writeln!(out, " wast_context")?;
|
||||||
|
writeln!(
|
||||||
|
out,
|
||||||
|
" .run_file(&*isa, Path::new(\"{}\"))",
|
||||||
|
path.display()
|
||||||
|
)?;
|
||||||
|
writeln!(out, " .expect(\"error running wast file\");",)?;
|
||||||
|
writeln!(out, " }}")?;
|
||||||
|
writeln!(out)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Rename tests which have the same name as Rust keywords.
|
||||||
|
fn avoid_keywords(name: &str) -> &str {
|
||||||
|
match name {
|
||||||
|
"if" => "if_",
|
||||||
|
"loop" => "loop_",
|
||||||
|
"type" => "type_",
|
||||||
|
"const" => "const_",
|
||||||
|
"return" => "return_",
|
||||||
|
other => other,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Ignore tests that aren't supported yet.
|
||||||
|
fn ignore(_testsuite: &str, _name: &str) -> bool {
|
||||||
|
false
|
||||||
|
}
|
||||||
@@ -132,14 +132,6 @@ impl<'module_environment> FuncEnvironment<'module_environment> {
|
|||||||
self.isa.frontend_config().pointer_type()
|
self.isa.frontend_config().pointer_type()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Transform the call argument list in preparation for making a call.
|
|
||||||
fn get_real_call_args(func: &Function, call_args: &[ir::Value]) -> Vec<ir::Value> {
|
|
||||||
let mut real_call_args = Vec::with_capacity(call_args.len() + 1);
|
|
||||||
real_call_args.extend_from_slice(call_args);
|
|
||||||
real_call_args.push(func.special_param(ArgumentPurpose::VMContext).unwrap());
|
|
||||||
real_call_args
|
|
||||||
}
|
|
||||||
|
|
||||||
fn vmctx(&mut self, func: &mut Function) -> ir::GlobalValue {
|
fn vmctx(&mut self, func: &mut Function) -> ir::GlobalValue {
|
||||||
self.vmctx.unwrap_or_else(|| {
|
self.vmctx.unwrap_or_else(|| {
|
||||||
let vmctx = func.create_global_value(ir::GlobalValueData::VMContext);
|
let vmctx = func.create_global_value(ir::GlobalValueData::VMContext);
|
||||||
@@ -539,15 +531,6 @@ impl<'module_environment> cranelift_wasm::FuncEnvironment for FuncEnvironment<'m
|
|||||||
|
|
||||||
let table_entry_addr = pos.ins().table_addr(pointer_type, table, callee, 0);
|
let table_entry_addr = pos.ins().table_addr(pointer_type, table, callee, 0);
|
||||||
|
|
||||||
// Dereference table_entry_addr to get the function address.
|
|
||||||
let mem_flags = ir::MemFlags::trusted();
|
|
||||||
let func_addr = pos.ins().load(
|
|
||||||
pointer_type,
|
|
||||||
mem_flags,
|
|
||||||
table_entry_addr,
|
|
||||||
i32::from(self.offsets.vmcaller_checked_anyfunc_func_ptr()),
|
|
||||||
);
|
|
||||||
|
|
||||||
// If necessary, check the signature.
|
// If necessary, check the signature.
|
||||||
match self.module.table_plans[table_index].style {
|
match self.module.table_plans[table_index].style {
|
||||||
TableStyle::CallerChecksSignature => {
|
TableStyle::CallerChecksSignature => {
|
||||||
@@ -597,7 +580,27 @@ impl<'module_environment> cranelift_wasm::FuncEnvironment for FuncEnvironment<'m
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let real_call_args = FuncEnvironment::get_real_call_args(pos.func, call_args);
|
// Dereference table_entry_addr to get the function address.
|
||||||
|
let mem_flags = ir::MemFlags::trusted();
|
||||||
|
let func_addr = pos.ins().load(
|
||||||
|
pointer_type,
|
||||||
|
mem_flags,
|
||||||
|
table_entry_addr,
|
||||||
|
i32::from(self.offsets.vmcaller_checked_anyfunc_func_ptr()),
|
||||||
|
);
|
||||||
|
|
||||||
|
let mut real_call_args = Vec::with_capacity(call_args.len() + 1);
|
||||||
|
real_call_args.extend_from_slice(call_args);
|
||||||
|
|
||||||
|
// Append the callee vmctx address.
|
||||||
|
let vmctx = pos.ins().load(
|
||||||
|
pointer_type,
|
||||||
|
mem_flags,
|
||||||
|
table_entry_addr,
|
||||||
|
i32::from(self.offsets.vmcaller_checked_anyfunc_vmctx()),
|
||||||
|
);
|
||||||
|
real_call_args.push(vmctx);
|
||||||
|
|
||||||
Ok(pos.ins().call_indirect(sig_ref, func_addr, &real_call_args))
|
Ok(pos.ins().call_indirect(sig_ref, func_addr, &real_call_args))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -608,10 +611,12 @@ impl<'module_environment> cranelift_wasm::FuncEnvironment for FuncEnvironment<'m
|
|||||||
callee: ir::FuncRef,
|
callee: ir::FuncRef,
|
||||||
call_args: &[ir::Value],
|
call_args: &[ir::Value],
|
||||||
) -> WasmResult<ir::Inst> {
|
) -> WasmResult<ir::Inst> {
|
||||||
let real_call_args = FuncEnvironment::get_real_call_args(pos.func, call_args);
|
let mut real_call_args = Vec::with_capacity(call_args.len() + 1);
|
||||||
|
real_call_args.extend_from_slice(call_args);
|
||||||
|
|
||||||
// Handle direct calls to locally-defined functions.
|
// Handle direct calls to locally-defined functions.
|
||||||
if !self.module.is_imported_function(callee_index) {
|
if !self.module.is_imported_function(callee_index) {
|
||||||
|
real_call_args.push(pos.func.special_param(ArgumentPurpose::VMContext).unwrap());
|
||||||
return Ok(pos.ins().call(callee, &real_call_args));
|
return Ok(pos.ins().call(callee, &real_call_args));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -623,9 +628,18 @@ impl<'module_environment> cranelift_wasm::FuncEnvironment for FuncEnvironment<'m
|
|||||||
let base = pos
|
let base = pos
|
||||||
.ins()
|
.ins()
|
||||||
.global_value(pointer_type, imported_functions_base);
|
.global_value(pointer_type, imported_functions_base);
|
||||||
let offset = self.offsets.index_vmfunction_body_import(callee_index);
|
|
||||||
let mem_flags = ir::MemFlags::trusted();
|
let mem_flags = ir::MemFlags::trusted();
|
||||||
let func_addr = pos.ins().load(pointer_type, mem_flags, base, offset);
|
|
||||||
|
// Load the callee address.
|
||||||
|
let body_offset = self.offsets.index_vmfunction_import_body(callee_index);
|
||||||
|
let func_addr = pos.ins().load(pointer_type, mem_flags, base, body_offset);
|
||||||
|
|
||||||
|
// Append the callee vmctx address.
|
||||||
|
let vmctx_offset = self.offsets.index_vmfunction_import_vmctx(callee_index);
|
||||||
|
let vmctx = pos.ins().load(pointer_type, mem_flags, base, vmctx_offset);
|
||||||
|
real_call_args.push(vmctx);
|
||||||
|
|
||||||
Ok(pos.ins().call_indirect(sig_ref, func_addr, &real_call_args))
|
Ok(pos.ins().call_indirect(sig_ref, func_addr, &real_call_args))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -48,10 +48,10 @@ mod vmoffsets;
|
|||||||
pub use compilation::{
|
pub use compilation::{
|
||||||
compile_module, Compilation, CompileError, RelocSink, Relocation, RelocationTarget, Relocations,
|
compile_module, Compilation, CompileError, RelocSink, Relocation, RelocationTarget, Relocations,
|
||||||
};
|
};
|
||||||
pub use module::{
|
pub use module::{Export, MemoryPlan, MemoryStyle, Module, TableElements, TablePlan, TableStyle};
|
||||||
DataInitializer, Export, MemoryPlan, MemoryStyle, Module, TableElements, TablePlan, TableStyle,
|
pub use module_environ::{
|
||||||
|
translate_signature, DataInitializer, ModuleEnvironment, ModuleTranslation,
|
||||||
};
|
};
|
||||||
pub use module_environ::{translate_signature, ModuleEnvironment, ModuleTranslation};
|
|
||||||
pub use tunables::Tunables;
|
pub use tunables::Tunables;
|
||||||
pub use vmoffsets::VMOffsets;
|
pub use vmoffsets::VMOffsets;
|
||||||
|
|
||||||
|
|||||||
@@ -75,7 +75,7 @@ impl MemoryStyle {
|
|||||||
|
|
||||||
/// A WebAssembly linear memory description along with our chosen style for
|
/// A WebAssembly linear memory description along with our chosen style for
|
||||||
/// implementing it.
|
/// implementing it.
|
||||||
#[derive(Debug)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct MemoryPlan {
|
pub struct MemoryPlan {
|
||||||
/// The WebAssembly linear memory description.
|
/// The WebAssembly linear memory description.
|
||||||
pub memory: Memory,
|
pub memory: Memory,
|
||||||
@@ -113,7 +113,7 @@ impl TableStyle {
|
|||||||
|
|
||||||
/// A WebAssembly table description along with our chosen style for
|
/// A WebAssembly table description along with our chosen style for
|
||||||
/// implementing it.
|
/// implementing it.
|
||||||
#[derive(Debug)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct TablePlan {
|
pub struct TablePlan {
|
||||||
/// The WebAssembly table description.
|
/// The WebAssembly table description.
|
||||||
pub table: cranelift_wasm::Table,
|
pub table: cranelift_wasm::Table,
|
||||||
@@ -277,34 +277,3 @@ impl Module {
|
|||||||
index.index() < self.imported_globals.len()
|
index.index() < self.imported_globals.len()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A data initializer for linear memory.
|
|
||||||
pub struct DataInitializer<'data> {
|
|
||||||
/// The index of the memory to initialize.
|
|
||||||
pub memory_index: MemoryIndex,
|
|
||||||
/// Optionally a globalvar base to initialize at.
|
|
||||||
pub base: Option<GlobalIndex>,
|
|
||||||
/// A constant offset to initialize at.
|
|
||||||
pub offset: usize,
|
|
||||||
/// The initialization data.
|
|
||||||
pub data: &'data [u8],
|
|
||||||
}
|
|
||||||
|
|
||||||
/// References to the input wasm data buffer to be decoded and processed later,
|
|
||||||
/// separately from the main module translation.
|
|
||||||
pub struct LazyContents<'data> {
|
|
||||||
/// References to the function bodies.
|
|
||||||
pub function_body_inputs: PrimaryMap<DefinedFuncIndex, &'data [u8]>,
|
|
||||||
|
|
||||||
/// References to the data initializers.
|
|
||||||
pub data_initializers: Vec<DataInitializer<'data>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'data> LazyContents<'data> {
|
|
||||||
pub fn new() -> Self {
|
|
||||||
Self {
|
|
||||||
function_body_inputs: PrimaryMap::new(),
|
|
||||||
data_initializers: Vec::new(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,12 +1,13 @@
|
|||||||
use cranelift_codegen::ir;
|
use cranelift_codegen::ir;
|
||||||
use cranelift_codegen::ir::{AbiParam, ArgumentPurpose};
|
use cranelift_codegen::ir::{AbiParam, ArgumentPurpose};
|
||||||
use cranelift_codegen::isa;
|
use cranelift_codegen::isa;
|
||||||
|
use cranelift_entity::PrimaryMap;
|
||||||
use cranelift_wasm::{
|
use cranelift_wasm::{
|
||||||
self, translate_module, FuncIndex, Global, GlobalIndex, Memory, MemoryIndex, SignatureIndex,
|
self, translate_module, DefinedFuncIndex, FuncIndex, Global, GlobalIndex, Memory, MemoryIndex,
|
||||||
Table, TableIndex, WasmResult,
|
SignatureIndex, Table, TableIndex, WasmResult,
|
||||||
};
|
};
|
||||||
use func_environ::FuncEnvironment;
|
use func_environ::FuncEnvironment;
|
||||||
use module::{DataInitializer, Export, LazyContents, MemoryPlan, Module, TableElements, TablePlan};
|
use module::{Export, MemoryPlan, Module, TableElements, TablePlan};
|
||||||
use std::clone::Clone;
|
use std::clone::Clone;
|
||||||
use std::string::String;
|
use std::string::String;
|
||||||
use std::vec::Vec;
|
use std::vec::Vec;
|
||||||
@@ -259,3 +260,34 @@ pub fn translate_signature(mut sig: ir::Signature, pointer_type: ir::Type) -> ir
|
|||||||
.push(AbiParam::special(pointer_type, ArgumentPurpose::VMContext));
|
.push(AbiParam::special(pointer_type, ArgumentPurpose::VMContext));
|
||||||
sig
|
sig
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A data initializer for linear memory.
|
||||||
|
pub struct DataInitializer<'data> {
|
||||||
|
/// The index of the memory to initialize.
|
||||||
|
pub memory_index: MemoryIndex,
|
||||||
|
/// Optionally a globalvar base to initialize at.
|
||||||
|
pub base: Option<GlobalIndex>,
|
||||||
|
/// A constant offset to initialize at.
|
||||||
|
pub offset: usize,
|
||||||
|
/// The initialization data.
|
||||||
|
pub data: &'data [u8],
|
||||||
|
}
|
||||||
|
|
||||||
|
/// References to the input wasm data buffer to be decoded and processed later,
|
||||||
|
/// separately from the main module translation.
|
||||||
|
pub struct LazyContents<'data> {
|
||||||
|
/// References to the function bodies.
|
||||||
|
pub function_body_inputs: PrimaryMap<DefinedFuncIndex, &'data [u8]>,
|
||||||
|
|
||||||
|
/// References to the data initializers.
|
||||||
|
pub data_initializers: Vec<DataInitializer<'data>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'data> LazyContents<'data> {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
function_body_inputs: PrimaryMap::new(),
|
||||||
|
data_initializers: Vec::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -20,6 +20,26 @@ impl VMOffsets {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Offsets for `VMFunctionImport`.
|
||||||
|
impl VMOffsets {
|
||||||
|
/// The offset of the `body` field.
|
||||||
|
#[allow(clippy::erasing_op)]
|
||||||
|
pub fn vmfunction_import_body(&self) -> u8 {
|
||||||
|
0 * self.pointer_size
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The offset of the `vmctx` field.
|
||||||
|
#[allow(clippy::identity_op)]
|
||||||
|
pub fn vmfunction_import_vmctx(&self) -> u8 {
|
||||||
|
1 * self.pointer_size
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return the size of `VMFunctionImport`.
|
||||||
|
pub fn size_of_vmfunction_import(&self) -> u8 {
|
||||||
|
2 * self.pointer_size
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Offsets for `*const VMFunctionBody`.
|
/// Offsets for `*const VMFunctionBody`.
|
||||||
impl VMOffsets {
|
impl VMOffsets {
|
||||||
/// The size of the `current_elements` field.
|
/// The size of the `current_elements` field.
|
||||||
@@ -174,9 +194,14 @@ impl VMOffsets {
|
|||||||
1 * self.pointer_size
|
1 * self.pointer_size
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The offset of the `vmctx` field.
|
||||||
|
pub fn vmcaller_checked_anyfunc_vmctx(&self) -> u8 {
|
||||||
|
2 * self.pointer_size
|
||||||
|
}
|
||||||
|
|
||||||
/// Return the size of `VMCallerCheckedAnyfunc`.
|
/// Return the size of `VMCallerCheckedAnyfunc`.
|
||||||
pub fn size_of_vmcaller_checked_anyfunc(&self) -> u8 {
|
pub fn size_of_vmcaller_checked_anyfunc(&self) -> u8 {
|
||||||
2 * self.pointer_size
|
3 * self.pointer_size
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -230,6 +255,17 @@ impl VMOffsets {
|
|||||||
8 * self.pointer_size
|
8 * self.pointer_size
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Return the offset from the `imported_functions` pointer to `VMFunctionImport` index `index`.
|
||||||
|
fn index_vmfunction_import(&self, index: FuncIndex) -> i32 {
|
||||||
|
cast::i32(
|
||||||
|
index
|
||||||
|
.as_u32()
|
||||||
|
.checked_mul(u32::from(self.size_of_vmfunction_import()))
|
||||||
|
.unwrap(),
|
||||||
|
)
|
||||||
|
.unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
/// Return the offset from the `imported_tables` pointer to `VMTableImport` index `index`.
|
/// Return the offset from the `imported_tables` pointer to `VMTableImport` index `index`.
|
||||||
fn index_vmtable_import(&self, index: TableIndex) -> i32 {
|
fn index_vmtable_import(&self, index: TableIndex) -> i32 {
|
||||||
cast::i32(
|
cast::i32(
|
||||||
@@ -286,14 +322,18 @@ impl VMOffsets {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Return the offset from the `imported_functions` pointer to the
|
/// Return the offset from the `imported_functions` pointer to the
|
||||||
/// `*const VMFunctionBody` index `index`.
|
/// `body` field in `*const VMFunctionBody` index `index`.
|
||||||
pub fn index_vmfunction_body_import(&self, index: FuncIndex) -> i32 {
|
pub fn index_vmfunction_import_body(&self, index: FuncIndex) -> i32 {
|
||||||
cast::i32(
|
self.index_vmfunction_import(index)
|
||||||
index
|
.checked_add(i32::from(self.vmfunction_import_body()))
|
||||||
.as_u32()
|
.unwrap()
|
||||||
.checked_mul(u32::from(self.size_of_vmfunction_body_ptr()))
|
}
|
||||||
.unwrap(),
|
|
||||||
)
|
/// Return the offset from the `imported_functions` pointer to the
|
||||||
|
/// `vmctx` field in `*const VMFunctionBody` index `index`.
|
||||||
|
pub fn index_vmfunction_import_vmctx(&self, index: FuncIndex) -> i32 {
|
||||||
|
self.index_vmfunction_import(index)
|
||||||
|
.checked_add(i32::from(self.vmfunction_import_vmctx()))
|
||||||
.unwrap()
|
.unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ use std::fmt;
|
|||||||
use std::string::String;
|
use std::string::String;
|
||||||
use std::vec::Vec;
|
use std::vec::Vec;
|
||||||
use wasmtime_environ::CompileError;
|
use wasmtime_environ::CompileError;
|
||||||
|
use wasmtime_runtime::InstantiationError;
|
||||||
|
|
||||||
/// A runtime value.
|
/// A runtime value.
|
||||||
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
|
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
|
||||||
@@ -110,10 +111,6 @@ pub enum ActionError {
|
|||||||
#[fail(display = "Unknown field: {}", _0)]
|
#[fail(display = "Unknown field: {}", _0)]
|
||||||
Field(String),
|
Field(String),
|
||||||
|
|
||||||
/// An index was out of bounds.
|
|
||||||
#[fail(display = "Index out of bounds: {}", _0)]
|
|
||||||
Index(u64),
|
|
||||||
|
|
||||||
/// The field was present but was the wrong kind (eg. function, table, global, or memory).
|
/// The field was present but was the wrong kind (eg. function, table, global, or memory).
|
||||||
#[fail(display = "Kind error: {}", _0)]
|
#[fail(display = "Kind error: {}", _0)]
|
||||||
Kind(String),
|
Kind(String),
|
||||||
@@ -126,9 +123,10 @@ pub enum ActionError {
|
|||||||
#[fail(display = "WebAssembly compilation error: {}", _0)]
|
#[fail(display = "WebAssembly compilation error: {}", _0)]
|
||||||
Compile(CompileError),
|
Compile(CompileError),
|
||||||
|
|
||||||
/// Some runtime resource was unavailable or insufficient.
|
/// Some runtime resource was unavailable or insufficient, or the start function
|
||||||
#[fail(display = "Runtime resource error: {}", _0)]
|
/// trapped.
|
||||||
Resource(String),
|
#[fail(display = "Instantiation error: {}", _0)]
|
||||||
|
Instantiate(InstantiationError),
|
||||||
|
|
||||||
/// Link error.
|
/// Link error.
|
||||||
#[fail(display = "Link error: {}", _0)]
|
#[fail(display = "Link error: {}", _0)]
|
||||||
|
|||||||
287
lib/execute/src/instance_plus.rs
Normal file
287
lib/execute/src/instance_plus.rs
Normal file
@@ -0,0 +1,287 @@
|
|||||||
|
use action::{ActionError, ActionOutcome, RuntimeValue};
|
||||||
|
use cranelift_codegen::{ir, isa};
|
||||||
|
use cranelift_entity::{BoxedSlice, PrimaryMap};
|
||||||
|
use cranelift_wasm::DefinedFuncIndex;
|
||||||
|
use jit_code::JITCode;
|
||||||
|
use link::link_module;
|
||||||
|
use resolver::Resolver;
|
||||||
|
use std::cmp::max;
|
||||||
|
use std::rc::Rc;
|
||||||
|
use std::slice;
|
||||||
|
use std::string::String;
|
||||||
|
use std::vec::Vec;
|
||||||
|
use std::{mem, ptr};
|
||||||
|
use trampoline_park::TrampolinePark;
|
||||||
|
use wasmtime_environ::{
|
||||||
|
compile_module, Compilation, CompileError, DataInitializer, Module, ModuleEnvironment, Tunables,
|
||||||
|
};
|
||||||
|
use wasmtime_runtime::{
|
||||||
|
wasmtime_call_trampoline, Export, Imports, Instance, InstantiationError, VMFunctionBody,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// `InstancePlus` holds an `Instance` and adds support for performing actions
|
||||||
|
/// such as the "invoke" command in wast.
|
||||||
|
///
|
||||||
|
/// TODO: Think of a better name.
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct InstancePlus {
|
||||||
|
/// The contained instance.
|
||||||
|
pub instance: Box<Instance>,
|
||||||
|
|
||||||
|
/// Trampolines for calling into JIT code.
|
||||||
|
trampolines: TrampolinePark,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl InstancePlus {
|
||||||
|
/// Create a new `InstancePlus` by compiling the wasm module in `data` and instatiating it.
|
||||||
|
pub fn new(
|
||||||
|
jit_code: &mut JITCode,
|
||||||
|
isa: &isa::TargetIsa,
|
||||||
|
data: &[u8],
|
||||||
|
resolver: &mut Resolver,
|
||||||
|
) -> Result<Self, ActionError> {
|
||||||
|
let mut module = Module::new();
|
||||||
|
|
||||||
|
// TODO: Allow the tunables to be overridden.
|
||||||
|
let tunables = Tunables::default();
|
||||||
|
|
||||||
|
let (lazy_function_body_inputs, lazy_data_initializers) = {
|
||||||
|
let environ = ModuleEnvironment::new(isa, &mut module, tunables);
|
||||||
|
|
||||||
|
let translation = environ
|
||||||
|
.translate(&data)
|
||||||
|
.map_err(|error| ActionError::Compile(CompileError::Wasm(error)))?;
|
||||||
|
|
||||||
|
(
|
||||||
|
translation.lazy.function_body_inputs,
|
||||||
|
translation.lazy.data_initializers,
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
|
let (compilation, relocations) = compile_module(&module, &lazy_function_body_inputs, isa)
|
||||||
|
.map_err(ActionError::Compile)?;
|
||||||
|
|
||||||
|
let allocated_functions = allocate_functions(jit_code, compilation).map_err(|message| {
|
||||||
|
ActionError::Instantiate(InstantiationError::Resource(format!(
|
||||||
|
"failed to allocate memory for functions: {}",
|
||||||
|
message
|
||||||
|
)))
|
||||||
|
})?;
|
||||||
|
|
||||||
|
let imports = link_module(&module, &allocated_functions, relocations, resolver)
|
||||||
|
.map_err(ActionError::Link)?;
|
||||||
|
|
||||||
|
// Gather up the pointers to the compiled functions.
|
||||||
|
let finished_functions: BoxedSlice<DefinedFuncIndex, *const VMFunctionBody> =
|
||||||
|
allocated_functions
|
||||||
|
.into_iter()
|
||||||
|
.map(|(_index, allocated)| {
|
||||||
|
let fatptr: *const [VMFunctionBody] = *allocated;
|
||||||
|
fatptr as *const VMFunctionBody
|
||||||
|
})
|
||||||
|
.collect::<PrimaryMap<_, _>>()
|
||||||
|
.into_boxed_slice();
|
||||||
|
|
||||||
|
// Make all code compiled thus far executable.
|
||||||
|
jit_code.publish();
|
||||||
|
|
||||||
|
Self::with_parts(
|
||||||
|
Rc::new(module),
|
||||||
|
finished_functions,
|
||||||
|
imports,
|
||||||
|
lazy_data_initializers,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Construct a new `InstancePlus` from the parts needed to produce an `Instance`.
|
||||||
|
pub fn with_parts(
|
||||||
|
module: Rc<Module>,
|
||||||
|
finished_functions: BoxedSlice<DefinedFuncIndex, *const VMFunctionBody>,
|
||||||
|
imports: Imports,
|
||||||
|
data_initializers: Vec<DataInitializer>,
|
||||||
|
) -> Result<Self, ActionError> {
|
||||||
|
let instance = Instance::new(module, finished_functions, imports, data_initializers)
|
||||||
|
.map_err(ActionError::Instantiate)?;
|
||||||
|
|
||||||
|
Ok(Self::with_instance(instance))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Construct a new `InstancePlus` from an existing instance.
|
||||||
|
pub fn with_instance(instance: Box<Instance>) -> Self {
|
||||||
|
Self {
|
||||||
|
instance,
|
||||||
|
trampolines: TrampolinePark::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Invoke a function in this `Instance` identified by an export name.
|
||||||
|
pub fn invoke(
|
||||||
|
&mut self,
|
||||||
|
jit_code: &mut JITCode,
|
||||||
|
isa: &isa::TargetIsa,
|
||||||
|
function_name: &str,
|
||||||
|
args: &[RuntimeValue],
|
||||||
|
) -> Result<ActionOutcome, ActionError> {
|
||||||
|
let (address, signature, callee_vmctx) = match self.instance.lookup(function_name) {
|
||||||
|
Some(Export::Function {
|
||||||
|
address,
|
||||||
|
signature,
|
||||||
|
vmctx,
|
||||||
|
}) => (address, signature, vmctx),
|
||||||
|
Some(_) => {
|
||||||
|
return Err(ActionError::Kind(format!(
|
||||||
|
"exported item \"{}\" is not a function",
|
||||||
|
function_name
|
||||||
|
)))
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
return Err(ActionError::Field(format!(
|
||||||
|
"no export named \"{}\"",
|
||||||
|
function_name
|
||||||
|
)))
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
for (index, value) in args.iter().enumerate() {
|
||||||
|
assert_eq!(value.value_type(), signature.params[index].value_type);
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Support values larger than u64.
|
||||||
|
let mut values_vec: Vec<u64> = Vec::new();
|
||||||
|
let value_size = mem::size_of::<u64>();
|
||||||
|
values_vec.resize(max(signature.params.len(), signature.returns.len()), 0u64);
|
||||||
|
|
||||||
|
// Store the argument values into `values_vec`.
|
||||||
|
for (index, arg) in args.iter().enumerate() {
|
||||||
|
unsafe {
|
||||||
|
let ptr = values_vec.as_mut_ptr().add(index);
|
||||||
|
|
||||||
|
match arg {
|
||||||
|
RuntimeValue::I32(x) => ptr::write(ptr as *mut i32, *x),
|
||||||
|
RuntimeValue::I64(x) => ptr::write(ptr as *mut i64, *x),
|
||||||
|
RuntimeValue::F32(x) => ptr::write(ptr as *mut u32, *x),
|
||||||
|
RuntimeValue::F64(x) => ptr::write(ptr as *mut u64, *x),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the trampoline to call for this function.
|
||||||
|
let exec_code_buf = self
|
||||||
|
.trampolines
|
||||||
|
.get(jit_code, isa, address, &signature, value_size)?;
|
||||||
|
|
||||||
|
// Make all JIT code produced thus far executable.
|
||||||
|
jit_code.publish();
|
||||||
|
|
||||||
|
// Call the trampoline.
|
||||||
|
if let Err(message) = unsafe {
|
||||||
|
wasmtime_call_trampoline(
|
||||||
|
exec_code_buf,
|
||||||
|
values_vec.as_mut_ptr() as *mut u8,
|
||||||
|
callee_vmctx,
|
||||||
|
)
|
||||||
|
} {
|
||||||
|
return Ok(ActionOutcome::Trapped { message });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load the return values out of `values_vec`.
|
||||||
|
let values = signature
|
||||||
|
.returns
|
||||||
|
.iter()
|
||||||
|
.enumerate()
|
||||||
|
.map(|(index, abi_param)| unsafe {
|
||||||
|
let ptr = values_vec.as_ptr().add(index);
|
||||||
|
|
||||||
|
match abi_param.value_type {
|
||||||
|
ir::types::I32 => RuntimeValue::I32(ptr::read(ptr as *const i32)),
|
||||||
|
ir::types::I64 => RuntimeValue::I64(ptr::read(ptr as *const i64)),
|
||||||
|
ir::types::F32 => RuntimeValue::F32(ptr::read(ptr as *const u32)),
|
||||||
|
ir::types::F64 => RuntimeValue::F64(ptr::read(ptr as *const u64)),
|
||||||
|
other => panic!("unsupported value type {:?}", other),
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
Ok(ActionOutcome::Returned { values })
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns a slice of the contents of allocated linear memory.
|
||||||
|
pub fn inspect_memory(
|
||||||
|
&self,
|
||||||
|
memory_name: &str,
|
||||||
|
start: usize,
|
||||||
|
len: usize,
|
||||||
|
) -> Result<&[u8], ActionError> {
|
||||||
|
let address = match unsafe { self.instance.lookup_immutable(memory_name) } {
|
||||||
|
Some(Export::Memory {
|
||||||
|
address,
|
||||||
|
memory: _memory,
|
||||||
|
vmctx: _vmctx,
|
||||||
|
}) => address,
|
||||||
|
Some(_) => {
|
||||||
|
return Err(ActionError::Kind(format!(
|
||||||
|
"exported item \"{}\" is not a linear memory",
|
||||||
|
memory_name
|
||||||
|
)))
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
return Err(ActionError::Field(format!(
|
||||||
|
"no export named \"{}\"",
|
||||||
|
memory_name
|
||||||
|
)))
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(unsafe {
|
||||||
|
let memory_def = &*address;
|
||||||
|
&slice::from_raw_parts(memory_def.base, memory_def.current_length)[start..start + len]
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Read a global in this `Instance` identified by an export name.
|
||||||
|
pub fn get(&self, global_name: &str) -> Result<RuntimeValue, ActionError> {
|
||||||
|
let (address, global) = match unsafe { self.instance.lookup_immutable(global_name) } {
|
||||||
|
Some(Export::Global { address, global }) => (address, global),
|
||||||
|
Some(_) => {
|
||||||
|
return Err(ActionError::Kind(format!(
|
||||||
|
"exported item \"{}\" is not a global variable",
|
||||||
|
global_name
|
||||||
|
)))
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
return Err(ActionError::Field(format!(
|
||||||
|
"no export named \"{}\"",
|
||||||
|
global_name
|
||||||
|
)))
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
let global_def = &*address;
|
||||||
|
Ok(match global.ty {
|
||||||
|
ir::types::I32 => RuntimeValue::I32(*global_def.as_i32()),
|
||||||
|
ir::types::I64 => RuntimeValue::I64(*global_def.as_i64()),
|
||||||
|
ir::types::F32 => RuntimeValue::F32(*global_def.as_f32_bits()),
|
||||||
|
ir::types::F64 => RuntimeValue::F64(*global_def.as_f64_bits()),
|
||||||
|
other => {
|
||||||
|
return Err(ActionError::Type(format!(
|
||||||
|
"global with type {} not supported",
|
||||||
|
other
|
||||||
|
)))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn allocate_functions(
|
||||||
|
jit_code: &mut JITCode,
|
||||||
|
compilation: Compilation,
|
||||||
|
) -> Result<PrimaryMap<DefinedFuncIndex, *mut [VMFunctionBody]>, String> {
|
||||||
|
let mut result = PrimaryMap::with_capacity(compilation.functions.len());
|
||||||
|
for (_, body) in compilation.functions.into_iter() {
|
||||||
|
let fatptr: *mut [VMFunctionBody] = jit_code.allocate_copy_of_byte_slice(body)?;
|
||||||
|
result.push(fatptr);
|
||||||
|
}
|
||||||
|
Ok(result)
|
||||||
|
}
|
||||||
@@ -7,15 +7,15 @@ use std::{cmp, mem};
|
|||||||
use wasmtime_runtime::{Mmap, VMFunctionBody};
|
use wasmtime_runtime::{Mmap, VMFunctionBody};
|
||||||
|
|
||||||
/// Memory manager for executable code.
|
/// Memory manager for executable code.
|
||||||
pub struct Code {
|
pub struct JITCode {
|
||||||
current: Mmap,
|
current: Mmap,
|
||||||
mmaps: Vec<Mmap>,
|
mmaps: Vec<Mmap>,
|
||||||
position: usize,
|
position: usize,
|
||||||
published: usize,
|
published: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Code {
|
impl JITCode {
|
||||||
/// Create a new `Code` instance.
|
/// Create a new `JITCode` instance.
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Self {
|
Self {
|
||||||
current: Mmap::new(),
|
current: Mmap::new(),
|
||||||
@@ -39,16 +39,17 @@ extern crate failure;
|
|||||||
extern crate failure_derive;
|
extern crate failure_derive;
|
||||||
|
|
||||||
mod action;
|
mod action;
|
||||||
mod code;
|
mod instance_plus;
|
||||||
mod export;
|
mod jit_code;
|
||||||
mod link;
|
mod link;
|
||||||
mod world;
|
mod resolver;
|
||||||
|
mod trampoline_park;
|
||||||
|
|
||||||
pub use action::{ActionError, ActionOutcome, RuntimeValue};
|
pub use action::{ActionError, ActionOutcome, RuntimeValue};
|
||||||
pub use code::Code;
|
pub use instance_plus::InstancePlus;
|
||||||
pub use export::{Export, NullResolver, Resolver};
|
pub use jit_code::JITCode;
|
||||||
pub use link::link_module;
|
pub use link::link_module;
|
||||||
pub use world::InstanceWorld;
|
pub use resolver::{NullResolver, Resolver};
|
||||||
|
|
||||||
#[cfg(not(feature = "std"))]
|
#[cfg(not(feature = "std"))]
|
||||||
mod std {
|
mod std {
|
||||||
|
|||||||
@@ -1,16 +1,18 @@
|
|||||||
use cranelift_codegen::binemit::Reloc;
|
use cranelift_codegen::binemit::Reloc;
|
||||||
use cranelift_entity::PrimaryMap;
|
use cranelift_entity::PrimaryMap;
|
||||||
use cranelift_wasm::{DefinedFuncIndex, Global, GlobalInit, Memory, Table, TableElementType};
|
use cranelift_wasm::{DefinedFuncIndex, Global, GlobalInit, Memory, Table, TableElementType};
|
||||||
use export::{Export, FunctionExport, Resolver};
|
use resolver::Resolver;
|
||||||
use std::ptr::write_unaligned;
|
use std::ptr::write_unaligned;
|
||||||
use std::string::String;
|
use std::string::String;
|
||||||
use std::vec::Vec;
|
use std::vec::Vec;
|
||||||
use wasmtime_environ::{
|
use wasmtime_environ::{
|
||||||
MemoryPlan, MemoryStyle, Module, Relocation, RelocationTarget, Relocations, TablePlan,
|
MemoryPlan, MemoryStyle, Module, Relocation, RelocationTarget, Relocations, TablePlan,
|
||||||
TableStyle,
|
|
||||||
};
|
};
|
||||||
use wasmtime_runtime::libcalls;
|
use wasmtime_runtime::libcalls;
|
||||||
use wasmtime_runtime::{Imports, VMFunctionBody, VMGlobalImport, VMMemoryImport, VMTableImport};
|
use wasmtime_runtime::{
|
||||||
|
Export, Imports, VMFunctionBody, VMFunctionImport, VMGlobalImport, VMMemoryImport,
|
||||||
|
VMTableImport,
|
||||||
|
};
|
||||||
|
|
||||||
/// A link error, such as incompatible or unmatched imports/exports.
|
/// A link error, such as incompatible or unmatched imports/exports.
|
||||||
#[derive(Fail, Debug)]
|
#[derive(Fail, Debug)]
|
||||||
@@ -28,7 +30,11 @@ pub fn link_module(
|
|||||||
for (index, (ref module_name, ref field)) in module.imported_funcs.iter() {
|
for (index, (ref module_name, ref field)) in module.imported_funcs.iter() {
|
||||||
match resolver.resolve(module_name, field) {
|
match resolver.resolve(module_name, field) {
|
||||||
Some(export_value) => match export_value {
|
Some(export_value) => match export_value {
|
||||||
Export::Function(FunctionExport { address, signature }) => {
|
Export::Function {
|
||||||
|
address,
|
||||||
|
signature,
|
||||||
|
vmctx,
|
||||||
|
} => {
|
||||||
let import_signature = &module.signatures[module.functions[index]];
|
let import_signature = &module.signatures[module.functions[index]];
|
||||||
if signature != *import_signature {
|
if signature != *import_signature {
|
||||||
// TODO: If the difference is in the calling convention,
|
// TODO: If the difference is in the calling convention,
|
||||||
@@ -39,7 +45,10 @@ pub fn link_module(
|
|||||||
signature, import_signature)
|
signature, import_signature)
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
function_imports.push(address);
|
function_imports.push(VMFunctionImport {
|
||||||
|
body: address,
|
||||||
|
vmctx,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
Export::Table { .. } | Export::Memory { .. } | Export::Global { .. } => {
|
Export::Table { .. } | Export::Memory { .. } | Export::Global { .. } => {
|
||||||
return Err(LinkError(format!(
|
return Err(LinkError(format!(
|
||||||
@@ -104,12 +113,28 @@ pub fn link_module(
|
|||||||
memory,
|
memory,
|
||||||
} => {
|
} => {
|
||||||
let import_memory = &module.memory_plans[index];
|
let import_memory = &module.memory_plans[index];
|
||||||
if is_memory_compatible(&memory, import_memory) {
|
if !is_memory_compatible(&memory, import_memory) {
|
||||||
return Err(LinkError(format!(
|
return Err(LinkError(format!(
|
||||||
"{}/{}: exported memory incompatible with memory import",
|
"{}/{}: exported memory incompatible with memory import",
|
||||||
module_name, field
|
module_name, field
|
||||||
)));
|
)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Sanity-check: Ensure that the imported memory has at least
|
||||||
|
// guard-page protections the importing module expects it to have.
|
||||||
|
match (memory.style, &import_memory.style) {
|
||||||
|
(
|
||||||
|
MemoryStyle::Static { bound },
|
||||||
|
MemoryStyle::Static {
|
||||||
|
bound: import_bound,
|
||||||
|
},
|
||||||
|
) => {
|
||||||
|
assert!(bound >= *import_bound);
|
||||||
|
}
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
assert!(memory.offset_guard_size >= import_memory.offset_guard_size);
|
||||||
|
|
||||||
memory_imports.push(VMMemoryImport {
|
memory_imports.push(VMMemoryImport {
|
||||||
from: address,
|
from: address,
|
||||||
vmctx,
|
vmctx,
|
||||||
@@ -161,17 +186,15 @@ pub fn link_module(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let imports = Imports::new(
|
// Apply relocations, now that we have virtual addresses for everything.
|
||||||
|
relocate(allocated_functions, relocations, &module);
|
||||||
|
|
||||||
|
Ok(Imports::new(
|
||||||
function_imports,
|
function_imports,
|
||||||
table_imports,
|
table_imports,
|
||||||
memory_imports,
|
memory_imports,
|
||||||
global_imports,
|
global_imports,
|
||||||
);
|
))
|
||||||
|
|
||||||
// Apply relocations, now that we have virtual addresses for everything.
|
|
||||||
relocate(&imports, allocated_functions, relocations, &module);
|
|
||||||
|
|
||||||
Ok(imports)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_global_compatible(exported: &Global, imported: &Global) -> bool {
|
fn is_global_compatible(exported: &Global, imported: &Global) -> bool {
|
||||||
@@ -193,14 +216,6 @@ fn is_global_compatible(exported: &Global, imported: &Global) -> bool {
|
|||||||
exported_ty == imported_ty && imported_mutability == exported_mutability
|
exported_ty == imported_ty && imported_mutability == exported_mutability
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_table_style_compatible(exported_style: &TableStyle, imported_style: &TableStyle) -> bool {
|
|
||||||
match exported_style {
|
|
||||||
TableStyle::CallerChecksSignature => match imported_style {
|
|
||||||
TableStyle::CallerChecksSignature => true,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn is_table_element_type_compatible(
|
fn is_table_element_type_compatible(
|
||||||
exported_type: TableElementType,
|
exported_type: TableElementType,
|
||||||
imported_type: TableElementType,
|
imported_type: TableElementType,
|
||||||
@@ -225,7 +240,7 @@ fn is_table_compatible(exported: &TablePlan, imported: &TablePlan) -> bool {
|
|||||||
minimum: exported_minimum,
|
minimum: exported_minimum,
|
||||||
maximum: exported_maximum,
|
maximum: exported_maximum,
|
||||||
},
|
},
|
||||||
style: exported_style,
|
style: _exported_style,
|
||||||
} = exported;
|
} = exported;
|
||||||
let TablePlan {
|
let TablePlan {
|
||||||
table:
|
table:
|
||||||
@@ -234,30 +249,14 @@ fn is_table_compatible(exported: &TablePlan, imported: &TablePlan) -> bool {
|
|||||||
minimum: imported_minimum,
|
minimum: imported_minimum,
|
||||||
maximum: imported_maximum,
|
maximum: imported_maximum,
|
||||||
},
|
},
|
||||||
style: imported_style,
|
style: _imported_style,
|
||||||
} = imported;
|
} = imported;
|
||||||
|
|
||||||
is_table_element_type_compatible(*exported_ty, *imported_ty)
|
is_table_element_type_compatible(*exported_ty, *imported_ty)
|
||||||
&& imported_minimum >= exported_minimum
|
&& imported_minimum <= exported_minimum
|
||||||
&& imported_maximum <= exported_maximum
|
&& (imported_maximum.is_none()
|
||||||
&& is_table_style_compatible(imported_style, exported_style)
|
|| (!exported_maximum.is_none()
|
||||||
}
|
&& imported_maximum.unwrap() >= exported_maximum.unwrap()))
|
||||||
|
|
||||||
fn is_memory_style_compatible(exported_style: &MemoryStyle, imported_style: &MemoryStyle) -> bool {
|
|
||||||
match exported_style {
|
|
||||||
MemoryStyle::Dynamic => match imported_style {
|
|
||||||
MemoryStyle::Dynamic => true,
|
|
||||||
_ => false,
|
|
||||||
},
|
|
||||||
MemoryStyle::Static {
|
|
||||||
bound: imported_bound,
|
|
||||||
} => match imported_style {
|
|
||||||
MemoryStyle::Static {
|
|
||||||
bound: exported_bound,
|
|
||||||
} => exported_bound >= imported_bound,
|
|
||||||
_ => false,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_memory_compatible(exported: &MemoryPlan, imported: &MemoryPlan) -> bool {
|
fn is_memory_compatible(exported: &MemoryPlan, imported: &MemoryPlan) -> bool {
|
||||||
@@ -268,8 +267,8 @@ fn is_memory_compatible(exported: &MemoryPlan, imported: &MemoryPlan) -> bool {
|
|||||||
maximum: exported_maximum,
|
maximum: exported_maximum,
|
||||||
shared: exported_shared,
|
shared: exported_shared,
|
||||||
},
|
},
|
||||||
style: exported_style,
|
style: _exported_style,
|
||||||
offset_guard_size: exported_offset_guard_size,
|
offset_guard_size: _exported_offset_guard_size,
|
||||||
} = exported;
|
} = exported;
|
||||||
let MemoryPlan {
|
let MemoryPlan {
|
||||||
memory:
|
memory:
|
||||||
@@ -278,20 +277,19 @@ fn is_memory_compatible(exported: &MemoryPlan, imported: &MemoryPlan) -> bool {
|
|||||||
maximum: imported_maximum,
|
maximum: imported_maximum,
|
||||||
shared: imported_shared,
|
shared: imported_shared,
|
||||||
},
|
},
|
||||||
style: imported_style,
|
style: _imported_style,
|
||||||
offset_guard_size: imported_offset_guard_size,
|
offset_guard_size: _imported_offset_guard_size,
|
||||||
} = imported;
|
} = imported;
|
||||||
|
|
||||||
imported_minimum >= exported_minimum
|
imported_minimum <= exported_minimum
|
||||||
&& imported_maximum <= exported_maximum
|
&& (imported_maximum.is_none()
|
||||||
|
|| (!exported_maximum.is_none()
|
||||||
|
&& imported_maximum.unwrap() >= exported_maximum.unwrap()))
|
||||||
&& exported_shared == imported_shared
|
&& exported_shared == imported_shared
|
||||||
&& is_memory_style_compatible(exported_style, imported_style)
|
|
||||||
&& exported_offset_guard_size >= imported_offset_guard_size
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Performs the relocations inside the function bytecode, provided the necessary metadata.
|
/// Performs the relocations inside the function bytecode, provided the necessary metadata.
|
||||||
fn relocate(
|
fn relocate(
|
||||||
imports: &Imports,
|
|
||||||
allocated_functions: &PrimaryMap<DefinedFuncIndex, *mut [VMFunctionBody]>,
|
allocated_functions: &PrimaryMap<DefinedFuncIndex, *mut [VMFunctionBody]>,
|
||||||
relocations: PrimaryMap<DefinedFuncIndex, Vec<Relocation>>,
|
relocations: PrimaryMap<DefinedFuncIndex, Vec<Relocation>>,
|
||||||
module: &Module,
|
module: &Module,
|
||||||
@@ -305,7 +303,7 @@ fn relocate(
|
|||||||
let fatptr: *const [VMFunctionBody] = allocated_functions[f];
|
let fatptr: *const [VMFunctionBody] = allocated_functions[f];
|
||||||
fatptr as *const VMFunctionBody as usize
|
fatptr as *const VMFunctionBody as usize
|
||||||
}
|
}
|
||||||
None => imports.functions[index] as usize,
|
None => panic!("direct call to import"),
|
||||||
},
|
},
|
||||||
RelocationTarget::Memory32Grow => wasmtime_memory32_grow as usize,
|
RelocationTarget::Memory32Grow => wasmtime_memory32_grow as usize,
|
||||||
RelocationTarget::Memory32Size => wasmtime_memory32_size as usize,
|
RelocationTarget::Memory32Size => wasmtime_memory32_size as usize,
|
||||||
|
|||||||
16
lib/execute/src/resolver.rs
Normal file
16
lib/execute/src/resolver.rs
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
use wasmtime_runtime::Export;
|
||||||
|
|
||||||
|
/// Import resolver connects imports with available exported values.
|
||||||
|
pub trait Resolver {
|
||||||
|
/// Resolve the given module/field combo.
|
||||||
|
fn resolve(&mut self, module: &str, field: &str) -> Option<Export>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// `Resolver` implementation that always resolves to `None`.
|
||||||
|
pub struct NullResolver {}
|
||||||
|
|
||||||
|
impl Resolver for NullResolver {
|
||||||
|
fn resolve(&mut self, _module: &str, _field: &str) -> Option<Export> {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
152
lib/execute/src/trampoline_park.rs
Normal file
152
lib/execute/src/trampoline_park.rs
Normal file
@@ -0,0 +1,152 @@
|
|||||||
|
use action::ActionError;
|
||||||
|
use cranelift_codegen::ir::InstBuilder;
|
||||||
|
use cranelift_codegen::Context;
|
||||||
|
use cranelift_codegen::{binemit, ir, isa};
|
||||||
|
use cranelift_frontend::{FunctionBuilder, FunctionBuilderContext};
|
||||||
|
use jit_code::JITCode;
|
||||||
|
use std::collections::HashMap;
|
||||||
|
use std::fmt;
|
||||||
|
use wasmtime_environ::{CompileError, RelocSink};
|
||||||
|
use wasmtime_runtime::{InstantiationError, VMFunctionBody};
|
||||||
|
|
||||||
|
pub struct TrampolinePark {
|
||||||
|
/// Memoized per-function trampolines.
|
||||||
|
memoized: HashMap<*const VMFunctionBody, *const VMFunctionBody>,
|
||||||
|
|
||||||
|
/// The `FunctionBuilderContext`, shared between function compilations.
|
||||||
|
fn_builder_ctx: FunctionBuilderContext,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TrampolinePark {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
memoized: HashMap::new(),
|
||||||
|
fn_builder_ctx: FunctionBuilderContext::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get(
|
||||||
|
&mut self,
|
||||||
|
jit_code: &mut JITCode,
|
||||||
|
isa: &isa::TargetIsa,
|
||||||
|
callee_address: *const VMFunctionBody,
|
||||||
|
signature: &ir::Signature,
|
||||||
|
value_size: usize,
|
||||||
|
) -> Result<*const VMFunctionBody, ActionError> {
|
||||||
|
use std::collections::hash_map::Entry::{Occupied, Vacant};
|
||||||
|
Ok(match self.memoized.entry(callee_address) {
|
||||||
|
Occupied(entry) => *entry.get(),
|
||||||
|
Vacant(entry) => {
|
||||||
|
let body = make_trampoline(
|
||||||
|
&mut self.fn_builder_ctx,
|
||||||
|
jit_code,
|
||||||
|
isa,
|
||||||
|
callee_address,
|
||||||
|
signature,
|
||||||
|
value_size,
|
||||||
|
)?;
|
||||||
|
entry.insert(body);
|
||||||
|
body
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Debug for TrampolinePark {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
// The `fn_builder_ctx` field is just a cache and has no logical state.
|
||||||
|
write!(f, "{:?}", self.memoized)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn make_trampoline(
|
||||||
|
fn_builder_ctx: &mut FunctionBuilderContext,
|
||||||
|
jit_code: &mut JITCode,
|
||||||
|
isa: &isa::TargetIsa,
|
||||||
|
callee_address: *const VMFunctionBody,
|
||||||
|
signature: &ir::Signature,
|
||||||
|
value_size: usize,
|
||||||
|
) -> Result<*const VMFunctionBody, ActionError> {
|
||||||
|
let pointer_type = isa.pointer_type();
|
||||||
|
let mut wrapper_sig = ir::Signature::new(isa.frontend_config().default_call_conv);
|
||||||
|
|
||||||
|
// Add the `values_vec` parameter.
|
||||||
|
wrapper_sig.params.push(ir::AbiParam::new(pointer_type));
|
||||||
|
// Add the `vmctx` parameter.
|
||||||
|
wrapper_sig.params.push(ir::AbiParam::special(
|
||||||
|
pointer_type,
|
||||||
|
ir::ArgumentPurpose::VMContext,
|
||||||
|
));
|
||||||
|
|
||||||
|
let mut context = Context::new();
|
||||||
|
context.func = ir::Function::with_name_signature(ir::ExternalName::user(0, 0), wrapper_sig);
|
||||||
|
|
||||||
|
{
|
||||||
|
let mut builder = FunctionBuilder::new(&mut context.func, fn_builder_ctx);
|
||||||
|
let block0 = builder.create_ebb();
|
||||||
|
|
||||||
|
builder.append_ebb_params_for_function_params(block0);
|
||||||
|
builder.switch_to_block(block0);
|
||||||
|
builder.seal_block(block0);
|
||||||
|
|
||||||
|
let mut callee_args = Vec::new();
|
||||||
|
let pointer_type = isa.pointer_type();
|
||||||
|
|
||||||
|
let (values_vec_ptr_val, vmctx_ptr_val) = {
|
||||||
|
let params = builder.func.dfg.ebb_params(block0);
|
||||||
|
(params[0], params[1])
|
||||||
|
};
|
||||||
|
|
||||||
|
// Load the argument values out of `values_vec`.
|
||||||
|
let mflags = ir::MemFlags::trusted();
|
||||||
|
for (i, r) in signature.params.iter().enumerate() {
|
||||||
|
let value = match r.purpose {
|
||||||
|
ir::ArgumentPurpose::Normal => builder.ins().load(
|
||||||
|
r.value_type,
|
||||||
|
mflags,
|
||||||
|
values_vec_ptr_val,
|
||||||
|
(i * value_size) as i32,
|
||||||
|
),
|
||||||
|
ir::ArgumentPurpose::VMContext => vmctx_ptr_val,
|
||||||
|
other => panic!("unsupported argument purpose {}", other),
|
||||||
|
};
|
||||||
|
callee_args.push(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
let new_sig = builder.import_signature(signature.clone());
|
||||||
|
|
||||||
|
// TODO: It's possible to make this a direct call. We just need Cranelift
|
||||||
|
// to support functions declared with an immediate integer address.
|
||||||
|
// ExternalName::Absolute(u64). Let's do it.
|
||||||
|
let callee_value = builder.ins().iconst(pointer_type, callee_address as i64);
|
||||||
|
let call = builder
|
||||||
|
.ins()
|
||||||
|
.call_indirect(new_sig, callee_value, &callee_args);
|
||||||
|
|
||||||
|
let results = builder.func.dfg.inst_results(call).to_vec();
|
||||||
|
|
||||||
|
// Store the return values into `values_vec`.
|
||||||
|
let mflags = ir::MemFlags::trusted();
|
||||||
|
for (i, r) in results.iter().enumerate() {
|
||||||
|
builder
|
||||||
|
.ins()
|
||||||
|
.store(mflags, *r, values_vec_ptr_val, (i * value_size) as i32);
|
||||||
|
}
|
||||||
|
|
||||||
|
builder.ins().return_(&[]);
|
||||||
|
builder.finalize()
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut code_buf: Vec<u8> = Vec::new();
|
||||||
|
let mut reloc_sink = RelocSink::new();
|
||||||
|
let mut trap_sink = binemit::NullTrapSink {};
|
||||||
|
context
|
||||||
|
.compile_and_emit(isa, &mut code_buf, &mut reloc_sink, &mut trap_sink)
|
||||||
|
.map_err(|error| ActionError::Compile(CompileError::Codegen(error)))?;
|
||||||
|
assert!(reloc_sink.func_relocs.is_empty());
|
||||||
|
|
||||||
|
Ok(jit_code
|
||||||
|
.allocate_copy_of_byte_slice(&code_buf)
|
||||||
|
.map_err(|message| ActionError::Instantiate(InstantiationError::Resource(message)))?
|
||||||
|
.as_ptr())
|
||||||
|
}
|
||||||
@@ -1,553 +0,0 @@
|
|||||||
use action::{ActionError, ActionOutcome, RuntimeValue};
|
|
||||||
use code::Code;
|
|
||||||
use cranelift_codegen::ir::InstBuilder;
|
|
||||||
use cranelift_codegen::Context;
|
|
||||||
use cranelift_codegen::{binemit, ir, isa};
|
|
||||||
use cranelift_entity::{BoxedSlice, EntityRef, PrimaryMap};
|
|
||||||
use cranelift_frontend::{FunctionBuilder, FunctionBuilderContext};
|
|
||||||
use cranelift_wasm::{
|
|
||||||
DefinedFuncIndex, DefinedGlobalIndex, DefinedMemoryIndex, DefinedTableIndex, FuncIndex,
|
|
||||||
GlobalIndex, MemoryIndex, TableIndex,
|
|
||||||
};
|
|
||||||
use export::Resolver;
|
|
||||||
use link::link_module;
|
|
||||||
use std::cmp::max;
|
|
||||||
use std::collections::HashMap;
|
|
||||||
use std::slice;
|
|
||||||
use std::string::String;
|
|
||||||
use std::vec::Vec;
|
|
||||||
use std::{mem, ptr};
|
|
||||||
use wasmtime_environ::{
|
|
||||||
compile_module, Compilation, CompileError, Export, Module, ModuleEnvironment, RelocSink,
|
|
||||||
Tunables,
|
|
||||||
};
|
|
||||||
use wasmtime_runtime::{
|
|
||||||
wasmtime_call_trampoline, wasmtime_init_eager, wasmtime_init_finish, Instance, VMContext,
|
|
||||||
VMFunctionBody, VMGlobalDefinition, VMGlobalImport, VMMemoryDefinition, VMMemoryImport,
|
|
||||||
VMTableDefinition, VMTableImport,
|
|
||||||
};
|
|
||||||
|
|
||||||
/// A module, an instance of that module, and accompanying compilation artifacts.
|
|
||||||
///
|
|
||||||
/// TODO: Rename and reorganize this.
|
|
||||||
pub struct InstanceWorld {
|
|
||||||
module: Module,
|
|
||||||
instance: Instance,
|
|
||||||
|
|
||||||
/// Pointers to functions in executable memory.
|
|
||||||
finished_functions: BoxedSlice<DefinedFuncIndex, *const VMFunctionBody>,
|
|
||||||
|
|
||||||
/// Trampolines for calling into JIT code.
|
|
||||||
trampolines: TrampolinePark,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl InstanceWorld {
|
|
||||||
/// Create a new `InstanceWorld` by compiling the wasm module in `data` and instatiating it.
|
|
||||||
///
|
|
||||||
/// `finished_functions` holds the function bodies
|
|
||||||
/// which have been placed in executable memory and linked.
|
|
||||||
pub fn new(
|
|
||||||
code: &mut Code,
|
|
||||||
isa: &isa::TargetIsa,
|
|
||||||
data: &[u8],
|
|
||||||
resolver: &mut Resolver,
|
|
||||||
) -> Result<Self, ActionError> {
|
|
||||||
let mut module = Module::new();
|
|
||||||
// TODO: Allow the tunables to be overridden.
|
|
||||||
let tunables = Tunables::default();
|
|
||||||
let (lazy_function_body_inputs, lazy_data_initializers) = {
|
|
||||||
let environ = ModuleEnvironment::new(isa, &mut module, tunables);
|
|
||||||
|
|
||||||
let translation = environ
|
|
||||||
.translate(&data)
|
|
||||||
.map_err(|error| ActionError::Compile(CompileError::Wasm(error)))?;
|
|
||||||
|
|
||||||
(
|
|
||||||
translation.lazy.function_body_inputs,
|
|
||||||
translation.lazy.data_initializers,
|
|
||||||
)
|
|
||||||
};
|
|
||||||
|
|
||||||
let (compilation, relocations) = compile_module(&module, &lazy_function_body_inputs, isa)
|
|
||||||
.map_err(ActionError::Compile)?;
|
|
||||||
|
|
||||||
let allocated_functions =
|
|
||||||
allocate_functions(code, compilation).map_err(ActionError::Resource)?;
|
|
||||||
|
|
||||||
let imports = link_module(&module, &allocated_functions, relocations, resolver)
|
|
||||||
.map_err(ActionError::Link)?;
|
|
||||||
|
|
||||||
let finished_functions: BoxedSlice<DefinedFuncIndex, *const VMFunctionBody> =
|
|
||||||
allocated_functions
|
|
||||||
.into_iter()
|
|
||||||
.map(|(_index, allocated)| {
|
|
||||||
let fatptr: *const [VMFunctionBody] = *allocated;
|
|
||||||
fatptr as *const VMFunctionBody
|
|
||||||
})
|
|
||||||
.collect::<PrimaryMap<_, _>>()
|
|
||||||
.into_boxed_slice();
|
|
||||||
|
|
||||||
let instance = Instance::new(
|
|
||||||
&module,
|
|
||||||
&finished_functions,
|
|
||||||
imports,
|
|
||||||
&lazy_data_initializers,
|
|
||||||
)
|
|
||||||
.map_err(ActionError::Resource)?;
|
|
||||||
|
|
||||||
let fn_builder_ctx = FunctionBuilderContext::new();
|
|
||||||
|
|
||||||
let mut result = Self {
|
|
||||||
module,
|
|
||||||
instance,
|
|
||||||
finished_functions,
|
|
||||||
trampolines: TrampolinePark {
|
|
||||||
memo: HashMap::new(),
|
|
||||||
fn_builder_ctx,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
// The WebAssembly spec specifies that the start function is
|
|
||||||
// invoked automatically at instantiation time.
|
|
||||||
match result.invoke_start_function(code, isa)? {
|
|
||||||
ActionOutcome::Returned { .. } => {}
|
|
||||||
ActionOutcome::Trapped { message } => {
|
|
||||||
// Instantiation fails if the start function traps.
|
|
||||||
return Err(ActionError::Start(message));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(result)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_imported_function(&self, index: FuncIndex) -> Option<*const VMFunctionBody> {
|
|
||||||
if index.index() < self.module.imported_funcs.len() {
|
|
||||||
Some(unsafe { self.instance.vmctx().imported_function(index) })
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: Add an accessor for table elements.
|
|
||||||
#[allow(dead_code)]
|
|
||||||
fn get_imported_table(&self, index: TableIndex) -> Option<&VMTableImport> {
|
|
||||||
if index.index() < self.module.imported_tables.len() {
|
|
||||||
Some(unsafe { self.instance.vmctx().imported_table(index) })
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_imported_memory(&self, index: MemoryIndex) -> Option<&VMMemoryImport> {
|
|
||||||
if index.index() < self.module.imported_memories.len() {
|
|
||||||
Some(unsafe { self.instance.vmctx().imported_memory(index) })
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_imported_global(&self, index: GlobalIndex) -> Option<&VMGlobalImport> {
|
|
||||||
if index.index() < self.module.imported_globals.len() {
|
|
||||||
Some(unsafe { self.instance.vmctx().imported_global(index) })
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_finished_function(&self, index: DefinedFuncIndex) -> Option<*const VMFunctionBody> {
|
|
||||||
self.finished_functions.get(index).cloned()
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: Add an accessor for table elements.
|
|
||||||
#[allow(dead_code)]
|
|
||||||
fn get_defined_table(&self, index: DefinedTableIndex) -> Option<&VMTableDefinition> {
|
|
||||||
if self.module.table_index(index).index() < self.module.table_plans.len() {
|
|
||||||
Some(unsafe { self.instance.vmctx().table(index) })
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_defined_memory(&self, index: DefinedMemoryIndex) -> Option<&VMMemoryDefinition> {
|
|
||||||
if self.module.memory_index(index).index() < self.module.memory_plans.len() {
|
|
||||||
Some(unsafe { self.instance.vmctx().memory(index) })
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_defined_global(&self, index: DefinedGlobalIndex) -> Option<&VMGlobalDefinition> {
|
|
||||||
if self.module.global_index(index).index() < self.module.globals.len() {
|
|
||||||
Some(unsafe { self.instance.vmctx().global(index) })
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Invoke a function in this `InstanceWorld` by name.
|
|
||||||
pub fn invoke(
|
|
||||||
&mut self,
|
|
||||||
code: &mut Code,
|
|
||||||
isa: &isa::TargetIsa,
|
|
||||||
function_name: &str,
|
|
||||||
args: &[RuntimeValue],
|
|
||||||
) -> Result<ActionOutcome, ActionError> {
|
|
||||||
let fn_index = match self.module.exports.get(function_name) {
|
|
||||||
Some(Export::Function(index)) => *index,
|
|
||||||
Some(_) => {
|
|
||||||
return Err(ActionError::Kind(format!(
|
|
||||||
"exported item \"{}\" is not a function",
|
|
||||||
function_name
|
|
||||||
)))
|
|
||||||
}
|
|
||||||
None => {
|
|
||||||
return Err(ActionError::Field(format!(
|
|
||||||
"no export named \"{}\"",
|
|
||||||
function_name
|
|
||||||
)))
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
self.invoke_by_index(code, isa, fn_index, args)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Invoke the WebAssembly start function of the instance, if one is present.
|
|
||||||
fn invoke_start_function(
|
|
||||||
&mut self,
|
|
||||||
code: &mut Code,
|
|
||||||
isa: &isa::TargetIsa,
|
|
||||||
) -> Result<ActionOutcome, ActionError> {
|
|
||||||
if let Some(start_index) = self.module.start_func {
|
|
||||||
self.invoke_by_index(code, isa, start_index, &[])
|
|
||||||
} else {
|
|
||||||
// No start function, just return nothing.
|
|
||||||
Ok(ActionOutcome::Returned { values: vec![] })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Calls the given indexed function, passing its return values and returning
|
|
||||||
/// its results.
|
|
||||||
fn invoke_by_index(
|
|
||||||
&mut self,
|
|
||||||
code: &mut Code,
|
|
||||||
isa: &isa::TargetIsa,
|
|
||||||
fn_index: FuncIndex,
|
|
||||||
args: &[RuntimeValue],
|
|
||||||
) -> Result<ActionOutcome, ActionError> {
|
|
||||||
let callee_address = match self.module.defined_func_index(fn_index) {
|
|
||||||
Some(def_fn_index) => self
|
|
||||||
.get_finished_function(def_fn_index)
|
|
||||||
.ok_or_else(|| ActionError::Index(def_fn_index.index() as u64))?,
|
|
||||||
None => self
|
|
||||||
.get_imported_function(fn_index)
|
|
||||||
.ok_or_else(|| ActionError::Index(fn_index.index() as u64))?,
|
|
||||||
};
|
|
||||||
|
|
||||||
// Rather than writing inline assembly to jump to the code region, we use the fact that
|
|
||||||
// the Rust ABI for calling a function with no arguments and no return values matches the one
|
|
||||||
// of the generated code. Thanks to this, we can transmute the code region into a first-class
|
|
||||||
// Rust function and call it.
|
|
||||||
// Ensure that our signal handlers are ready for action.
|
|
||||||
wasmtime_init_eager();
|
|
||||||
wasmtime_init_finish(self.instance.vmctx_mut());
|
|
||||||
|
|
||||||
let signature = &self.module.signatures[self.module.functions[fn_index]];
|
|
||||||
let vmctx: *mut VMContext = self.instance.vmctx_mut();
|
|
||||||
|
|
||||||
for (index, value) in args.iter().enumerate() {
|
|
||||||
assert_eq!(value.value_type(), signature.params[index].value_type);
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: Support values larger than u64.
|
|
||||||
let mut values_vec: Vec<u64> = Vec::new();
|
|
||||||
let value_size = mem::size_of::<u64>();
|
|
||||||
values_vec.resize(max(signature.params.len(), signature.returns.len()), 0u64);
|
|
||||||
|
|
||||||
// Store the argument values into `values_vec`.
|
|
||||||
for (index, arg) in args.iter().enumerate() {
|
|
||||||
unsafe {
|
|
||||||
let ptr = values_vec.as_mut_ptr().add(index);
|
|
||||||
|
|
||||||
match arg {
|
|
||||||
RuntimeValue::I32(x) => ptr::write(ptr as *mut i32, *x),
|
|
||||||
RuntimeValue::I64(x) => ptr::write(ptr as *mut i64, *x),
|
|
||||||
RuntimeValue::F32(x) => ptr::write(ptr as *mut u32, *x),
|
|
||||||
RuntimeValue::F64(x) => ptr::write(ptr as *mut u64, *x),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Store the vmctx value into `values_vec`.
|
|
||||||
unsafe {
|
|
||||||
let ptr = values_vec.as_mut_ptr().add(args.len());
|
|
||||||
ptr::write(ptr as *mut usize, vmctx as usize)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get the trampoline to call for this function.
|
|
||||||
let exec_code_buf =
|
|
||||||
self.trampolines
|
|
||||||
.get(code, isa, callee_address, &signature, value_size)?;
|
|
||||||
|
|
||||||
// Make all JIT code produced thus far executable.
|
|
||||||
code.publish();
|
|
||||||
|
|
||||||
// Call the trampoline.
|
|
||||||
if let Err(message) = unsafe {
|
|
||||||
wasmtime_call_trampoline(
|
|
||||||
exec_code_buf,
|
|
||||||
values_vec.as_mut_ptr() as *mut u8,
|
|
||||||
self.instance.vmctx_mut(),
|
|
||||||
)
|
|
||||||
} {
|
|
||||||
return Ok(ActionOutcome::Trapped { message });
|
|
||||||
}
|
|
||||||
|
|
||||||
// Load the return values out of `values_vec`.
|
|
||||||
let values = signature
|
|
||||||
.returns
|
|
||||||
.iter()
|
|
||||||
.enumerate()
|
|
||||||
.map(|(index, abi_param)| unsafe {
|
|
||||||
let ptr = values_vec.as_ptr().add(index);
|
|
||||||
|
|
||||||
match abi_param.value_type {
|
|
||||||
ir::types::I32 => RuntimeValue::I32(ptr::read(ptr as *const i32)),
|
|
||||||
ir::types::I64 => RuntimeValue::I64(ptr::read(ptr as *const i64)),
|
|
||||||
ir::types::F32 => RuntimeValue::F32(ptr::read(ptr as *const u32)),
|
|
||||||
ir::types::F64 => RuntimeValue::F64(ptr::read(ptr as *const u64)),
|
|
||||||
other => panic!("unsupported value type {:?}", other),
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
Ok(ActionOutcome::Returned { values })
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Read a global in this `InstanceWorld` by name.
|
|
||||||
pub fn get(&self, global_name: &str) -> Result<RuntimeValue, ActionError> {
|
|
||||||
let global_index = match self.module.exports.get(global_name) {
|
|
||||||
Some(Export::Global(index)) => *index,
|
|
||||||
Some(_) => {
|
|
||||||
return Err(ActionError::Kind(format!(
|
|
||||||
"exported item \"{}\" is not a global",
|
|
||||||
global_name
|
|
||||||
)))
|
|
||||||
}
|
|
||||||
None => {
|
|
||||||
return Err(ActionError::Field(format!(
|
|
||||||
"no export named \"{}\"",
|
|
||||||
global_name
|
|
||||||
)))
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
self.get_by_index(global_index)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Reads the value of the indexed global variable in `module`.
|
|
||||||
pub fn get_by_index(&self, global_index: GlobalIndex) -> Result<RuntimeValue, ActionError> {
|
|
||||||
let global_address = match self.module.defined_global_index(global_index) {
|
|
||||||
Some(def_global_index) => self
|
|
||||||
.get_defined_global(def_global_index)
|
|
||||||
.ok_or_else(|| ActionError::Index(def_global_index.index() as u64))?,
|
|
||||||
None => {
|
|
||||||
let from: *const VMGlobalDefinition = self
|
|
||||||
.get_imported_global(global_index)
|
|
||||||
.ok_or_else(|| ActionError::Index(global_index.index() as u64))?
|
|
||||||
.from;
|
|
||||||
from
|
|
||||||
}
|
|
||||||
};
|
|
||||||
let global_def = unsafe { &*global_address };
|
|
||||||
|
|
||||||
unsafe {
|
|
||||||
Ok(
|
|
||||||
match self
|
|
||||||
.module
|
|
||||||
.globals
|
|
||||||
.get(global_index)
|
|
||||||
.ok_or_else(|| ActionError::Index(global_index.index() as u64))?
|
|
||||||
.ty
|
|
||||||
{
|
|
||||||
ir::types::I32 => RuntimeValue::I32(*global_def.as_i32()),
|
|
||||||
ir::types::I64 => RuntimeValue::I64(*global_def.as_i64()),
|
|
||||||
ir::types::F32 => RuntimeValue::F32(*global_def.as_f32_bits()),
|
|
||||||
ir::types::F64 => RuntimeValue::F64(*global_def.as_f64_bits()),
|
|
||||||
other => {
|
|
||||||
return Err(ActionError::Type(format!(
|
|
||||||
"global with type {} not supported",
|
|
||||||
other
|
|
||||||
)))
|
|
||||||
}
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns a slice of the contents of allocated linear memory.
|
|
||||||
pub fn inspect_memory(
|
|
||||||
&self,
|
|
||||||
memory_index: MemoryIndex,
|
|
||||||
address: usize,
|
|
||||||
len: usize,
|
|
||||||
) -> Result<&[u8], ActionError> {
|
|
||||||
let memory_address = match self.module.defined_memory_index(memory_index) {
|
|
||||||
Some(def_memory_index) => self
|
|
||||||
.get_defined_memory(def_memory_index)
|
|
||||||
.ok_or_else(|| ActionError::Index(def_memory_index.index() as u64))?,
|
|
||||||
None => {
|
|
||||||
let from: *const VMMemoryDefinition = self
|
|
||||||
.get_imported_memory(memory_index)
|
|
||||||
.ok_or_else(|| ActionError::Index(memory_index.index() as u64))?
|
|
||||||
.from;
|
|
||||||
from
|
|
||||||
}
|
|
||||||
};
|
|
||||||
let memory_def = unsafe { &*memory_address };
|
|
||||||
|
|
||||||
Ok(unsafe {
|
|
||||||
&slice::from_raw_parts(memory_def.base, memory_def.current_length)
|
|
||||||
[address..address + len]
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn allocate_functions(
|
|
||||||
code: &mut Code,
|
|
||||||
compilation: Compilation,
|
|
||||||
) -> Result<PrimaryMap<DefinedFuncIndex, *mut [VMFunctionBody]>, String> {
|
|
||||||
let mut result = PrimaryMap::with_capacity(compilation.functions.len());
|
|
||||||
for (_, body) in compilation.functions.into_iter() {
|
|
||||||
let fatptr: *mut [VMFunctionBody] = code.allocate_copy_of_byte_slice(body)?;
|
|
||||||
result.push(fatptr);
|
|
||||||
}
|
|
||||||
Ok(result)
|
|
||||||
}
|
|
||||||
|
|
||||||
struct TrampolinePark {
|
|
||||||
/// Memorized per-function trampolines.
|
|
||||||
memo: HashMap<*const VMFunctionBody, *const VMFunctionBody>,
|
|
||||||
|
|
||||||
/// The `FunctionBuilderContext`, shared between function compilations.
|
|
||||||
fn_builder_ctx: FunctionBuilderContext,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl TrampolinePark {
|
|
||||||
fn get(
|
|
||||||
&mut self,
|
|
||||||
code: &mut Code,
|
|
||||||
isa: &isa::TargetIsa,
|
|
||||||
callee_address: *const VMFunctionBody,
|
|
||||||
signature: &ir::Signature,
|
|
||||||
value_size: usize,
|
|
||||||
) -> Result<*const VMFunctionBody, ActionError> {
|
|
||||||
use std::collections::hash_map::Entry::{Occupied, Vacant};
|
|
||||||
Ok(match self.memo.entry(callee_address) {
|
|
||||||
Occupied(entry) => *entry.get(),
|
|
||||||
Vacant(entry) => {
|
|
||||||
let body = make_trampoline(
|
|
||||||
&mut self.fn_builder_ctx,
|
|
||||||
code,
|
|
||||||
isa,
|
|
||||||
callee_address,
|
|
||||||
signature,
|
|
||||||
value_size,
|
|
||||||
)?;
|
|
||||||
entry.insert(body);
|
|
||||||
body
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn make_trampoline(
|
|
||||||
fn_builder_ctx: &mut FunctionBuilderContext,
|
|
||||||
code: &mut Code,
|
|
||||||
isa: &isa::TargetIsa,
|
|
||||||
callee_address: *const VMFunctionBody,
|
|
||||||
signature: &ir::Signature,
|
|
||||||
value_size: usize,
|
|
||||||
) -> Result<*const VMFunctionBody, ActionError> {
|
|
||||||
let pointer_type = isa.pointer_type();
|
|
||||||
let mut wrapper_sig = ir::Signature::new(isa.frontend_config().default_call_conv);
|
|
||||||
|
|
||||||
// Add the `values_vec` parameter.
|
|
||||||
wrapper_sig.params.push(ir::AbiParam::new(pointer_type));
|
|
||||||
// Add the `vmctx` parameter.
|
|
||||||
wrapper_sig.params.push(ir::AbiParam::special(
|
|
||||||
pointer_type,
|
|
||||||
ir::ArgumentPurpose::VMContext,
|
|
||||||
));
|
|
||||||
|
|
||||||
let mut context = Context::new();
|
|
||||||
context.func = ir::Function::with_name_signature(ir::ExternalName::user(0, 0), wrapper_sig);
|
|
||||||
|
|
||||||
{
|
|
||||||
let mut builder = FunctionBuilder::new(&mut context.func, fn_builder_ctx);
|
|
||||||
let block0 = builder.create_ebb();
|
|
||||||
|
|
||||||
builder.append_ebb_params_for_function_params(block0);
|
|
||||||
builder.switch_to_block(block0);
|
|
||||||
builder.seal_block(block0);
|
|
||||||
|
|
||||||
let mut callee_args = Vec::new();
|
|
||||||
let pointer_type = isa.pointer_type();
|
|
||||||
|
|
||||||
let (values_vec_ptr_val, vmctx_ptr_val) = {
|
|
||||||
let params = builder.func.dfg.ebb_params(block0);
|
|
||||||
(params[0], params[1])
|
|
||||||
};
|
|
||||||
|
|
||||||
// Load the argument values out of `values_vec`.
|
|
||||||
let mflags = ir::MemFlags::trusted();
|
|
||||||
for (i, r) in signature.params.iter().enumerate() {
|
|
||||||
let value = match r.purpose {
|
|
||||||
ir::ArgumentPurpose::Normal => builder.ins().load(
|
|
||||||
r.value_type,
|
|
||||||
mflags,
|
|
||||||
values_vec_ptr_val,
|
|
||||||
(i * value_size) as i32,
|
|
||||||
),
|
|
||||||
ir::ArgumentPurpose::VMContext => vmctx_ptr_val,
|
|
||||||
other => panic!("unsupported argument purpose {}", other),
|
|
||||||
};
|
|
||||||
callee_args.push(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
let new_sig = builder.import_signature(signature.clone());
|
|
||||||
|
|
||||||
// TODO: It's possible to make this a direct call. We just need Cranelift
|
|
||||||
// to support functions declared with an immediate integer address.
|
|
||||||
// ExternalName::Absolute(u64). Let's do it.
|
|
||||||
let callee_value = builder.ins().iconst(pointer_type, callee_address as i64);
|
|
||||||
let call = builder
|
|
||||||
.ins()
|
|
||||||
.call_indirect(new_sig, callee_value, &callee_args);
|
|
||||||
|
|
||||||
let results = builder.func.dfg.inst_results(call).to_vec();
|
|
||||||
|
|
||||||
// Store the return values into `values_vec`.
|
|
||||||
let mflags = ir::MemFlags::trusted();
|
|
||||||
for (i, r) in results.iter().enumerate() {
|
|
||||||
builder
|
|
||||||
.ins()
|
|
||||||
.store(mflags, *r, values_vec_ptr_val, (i * value_size) as i32);
|
|
||||||
}
|
|
||||||
|
|
||||||
builder.ins().return_(&[]);
|
|
||||||
builder.finalize()
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut code_buf: Vec<u8> = Vec::new();
|
|
||||||
let mut reloc_sink = RelocSink::new();
|
|
||||||
let mut trap_sink = binemit::NullTrapSink {};
|
|
||||||
context
|
|
||||||
.compile_and_emit(isa, &mut code_buf, &mut reloc_sink, &mut trap_sink)
|
|
||||||
.map_err(|error| ActionError::Compile(CompileError::Codegen(error)))?;
|
|
||||||
assert!(reloc_sink.func_relocs.is_empty());
|
|
||||||
|
|
||||||
Ok(code
|
|
||||||
.allocate_copy_of_byte_slice(&code_buf)
|
|
||||||
.map_err(ActionError::Resource)?
|
|
||||||
.as_ptr())
|
|
||||||
}
|
|
||||||
@@ -1,22 +1,21 @@
|
|||||||
use cranelift_codegen::ir;
|
use cranelift_codegen::ir;
|
||||||
use cranelift_wasm::Global;
|
use cranelift_wasm::Global;
|
||||||
use wasmtime_environ::{MemoryPlan, TablePlan};
|
use vmcontext::{
|
||||||
use wasmtime_runtime::{
|
|
||||||
VMContext, VMFunctionBody, VMGlobalDefinition, VMMemoryDefinition, VMTableDefinition,
|
VMContext, VMFunctionBody, VMGlobalDefinition, VMMemoryDefinition, VMTableDefinition,
|
||||||
};
|
};
|
||||||
|
use wasmtime_environ::{MemoryPlan, TablePlan};
|
||||||
/// An exported function.
|
|
||||||
pub struct FunctionExport {
|
|
||||||
/// The address of the native-code function.
|
|
||||||
pub address: *const VMFunctionBody,
|
|
||||||
/// The function signature declaration, used for compatibilty checking.
|
|
||||||
pub signature: ir::Signature,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The value of an export passed from one instance to another.
|
/// The value of an export passed from one instance to another.
|
||||||
pub enum Export {
|
pub enum Export {
|
||||||
/// A function export value.
|
/// A function export value.
|
||||||
Function(FunctionExport),
|
Function {
|
||||||
|
/// The address of the native-code function.
|
||||||
|
address: *const VMFunctionBody,
|
||||||
|
/// The function signature declaration, used for compatibilty checking.
|
||||||
|
signature: ir::Signature,
|
||||||
|
/// Pointer to the containing VMContext.
|
||||||
|
vmctx: *mut VMContext,
|
||||||
|
},
|
||||||
|
|
||||||
/// A table export value.
|
/// A table export value.
|
||||||
Table {
|
Table {
|
||||||
@@ -49,8 +48,16 @@ pub enum Export {
|
|||||||
|
|
||||||
impl Export {
|
impl Export {
|
||||||
/// Construct a function export value.
|
/// Construct a function export value.
|
||||||
pub fn function(address: *const VMFunctionBody, signature: ir::Signature) -> Self {
|
pub fn function(
|
||||||
Export::Function(FunctionExport { address, signature })
|
address: *const VMFunctionBody,
|
||||||
|
signature: ir::Signature,
|
||||||
|
vmctx: *mut VMContext,
|
||||||
|
) -> Self {
|
||||||
|
Export::Function {
|
||||||
|
address,
|
||||||
|
signature,
|
||||||
|
vmctx,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Construct a table export value.
|
/// Construct a table export value.
|
||||||
@@ -80,18 +87,3 @@ impl Export {
|
|||||||
Export::Global { address, global }
|
Export::Global { address, global }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Import resolver connects imports with available exported values.
|
|
||||||
pub trait Resolver {
|
|
||||||
/// Resolve the given module/field combo.
|
|
||||||
fn resolve(&mut self, module: &str, field: &str) -> Option<Export>;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// `Resolver` implementation that always resolves to `None`.
|
|
||||||
pub struct NullResolver {}
|
|
||||||
|
|
||||||
impl Resolver for NullResolver {
|
|
||||||
fn resolve(&mut self, _module: &str, _field: &str) -> Option<Export> {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,12 +1,12 @@
|
|||||||
use cranelift_entity::{BoxedSlice, PrimaryMap};
|
use cranelift_entity::{BoxedSlice, PrimaryMap};
|
||||||
use cranelift_wasm::{FuncIndex, GlobalIndex, MemoryIndex, TableIndex};
|
use cranelift_wasm::{FuncIndex, GlobalIndex, MemoryIndex, TableIndex};
|
||||||
use vmcontext::{VMFunctionBody, VMGlobalImport, VMMemoryImport, VMTableImport};
|
use vmcontext::{VMFunctionImport, VMGlobalImport, VMMemoryImport, VMTableImport};
|
||||||
|
|
||||||
/// Resolved import pointers.
|
/// Resolved import pointers.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Imports {
|
pub struct Imports {
|
||||||
/// Resolved addresses for imported functions.
|
/// Resolved addresses for imported functions.
|
||||||
pub functions: BoxedSlice<FuncIndex, *const VMFunctionBody>,
|
pub functions: BoxedSlice<FuncIndex, VMFunctionImport>,
|
||||||
|
|
||||||
/// Resolved addresses for imported tables.
|
/// Resolved addresses for imported tables.
|
||||||
pub tables: BoxedSlice<TableIndex, VMTableImport>,
|
pub tables: BoxedSlice<TableIndex, VMTableImport>,
|
||||||
@@ -21,7 +21,7 @@ pub struct Imports {
|
|||||||
impl Imports {
|
impl Imports {
|
||||||
/// Construct a new `Imports` instance.
|
/// Construct a new `Imports` instance.
|
||||||
pub fn new(
|
pub fn new(
|
||||||
function_imports: PrimaryMap<FuncIndex, *const VMFunctionBody>,
|
function_imports: PrimaryMap<FuncIndex, VMFunctionImport>,
|
||||||
table_imports: PrimaryMap<TableIndex, VMTableImport>,
|
table_imports: PrimaryMap<TableIndex, VMTableImport>,
|
||||||
memory_imports: PrimaryMap<MemoryIndex, VMMemoryImport>,
|
memory_imports: PrimaryMap<MemoryIndex, VMMemoryImport>,
|
||||||
global_imports: PrimaryMap<GlobalIndex, VMGlobalImport>,
|
global_imports: PrimaryMap<GlobalIndex, VMGlobalImport>,
|
||||||
|
|||||||
@@ -4,13 +4,18 @@
|
|||||||
use cranelift_entity::EntityRef;
|
use cranelift_entity::EntityRef;
|
||||||
use cranelift_entity::{BoxedSlice, PrimaryMap};
|
use cranelift_entity::{BoxedSlice, PrimaryMap};
|
||||||
use cranelift_wasm::{
|
use cranelift_wasm::{
|
||||||
DefinedFuncIndex, DefinedGlobalIndex, DefinedMemoryIndex, DefinedTableIndex, FuncIndex,
|
DefinedFuncIndex, DefinedGlobalIndex, DefinedMemoryIndex, DefinedTableIndex, GlobalInit,
|
||||||
};
|
};
|
||||||
|
use export::Export;
|
||||||
use imports::Imports;
|
use imports::Imports;
|
||||||
use memory::LinearMemory;
|
use memory::LinearMemory;
|
||||||
use sig_registry::SignatureRegistry;
|
use sig_registry::SignatureRegistry;
|
||||||
|
use signalhandlers::{wasmtime_init_eager, wasmtime_init_finish};
|
||||||
|
use std::rc::Rc;
|
||||||
|
use std::slice;
|
||||||
use std::string::String;
|
use std::string::String;
|
||||||
use table::Table;
|
use table::Table;
|
||||||
|
use traphandlers::wasmtime_call;
|
||||||
use vmcontext::{
|
use vmcontext::{
|
||||||
VMCallerCheckedAnyfunc, VMContext, VMFunctionBody, VMGlobalDefinition, VMMemoryDefinition,
|
VMCallerCheckedAnyfunc, VMContext, VMFunctionBody, VMGlobalDefinition, VMMemoryDefinition,
|
||||||
VMTableDefinition,
|
VMTableDefinition,
|
||||||
@@ -20,6 +25,9 @@ use wasmtime_environ::{DataInitializer, Module};
|
|||||||
/// An Instance of a WebAssemby module.
|
/// An Instance of a WebAssemby module.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Instance {
|
pub struct Instance {
|
||||||
|
/// The `Module` this `Instance` was instantiated from.
|
||||||
|
module: Rc<Module>,
|
||||||
|
|
||||||
/// WebAssembly linear memory data.
|
/// WebAssembly linear memory data.
|
||||||
memories: BoxedSlice<DefinedMemoryIndex, LinearMemory>,
|
memories: BoxedSlice<DefinedMemoryIndex, LinearMemory>,
|
||||||
|
|
||||||
@@ -33,6 +41,9 @@ pub struct Instance {
|
|||||||
/// Resolved imports.
|
/// Resolved imports.
|
||||||
vmctx_imports: Imports,
|
vmctx_imports: Imports,
|
||||||
|
|
||||||
|
/// Pointers to functions in executable memory.
|
||||||
|
finished_functions: BoxedSlice<DefinedFuncIndex, *const VMFunctionBody>,
|
||||||
|
|
||||||
/// Table storage base address vector pointed to by vmctx.
|
/// Table storage base address vector pointed to by vmctx.
|
||||||
vmctx_tables: BoxedSlice<DefinedTableIndex, VMTableDefinition>,
|
vmctx_tables: BoxedSlice<DefinedTableIndex, VMTableDefinition>,
|
||||||
|
|
||||||
@@ -49,19 +60,20 @@ pub struct Instance {
|
|||||||
impl Instance {
|
impl Instance {
|
||||||
/// Create a new `Instance`.
|
/// Create a new `Instance`.
|
||||||
pub fn new(
|
pub fn new(
|
||||||
module: &Module,
|
module: Rc<Module>,
|
||||||
finished_functions: &BoxedSlice<DefinedFuncIndex, *const VMFunctionBody>,
|
finished_functions: BoxedSlice<DefinedFuncIndex, *const VMFunctionBody>,
|
||||||
mut vmctx_imports: Imports,
|
mut vmctx_imports: Imports,
|
||||||
data_initializers: &[DataInitializer],
|
data_initializers: Vec<DataInitializer>,
|
||||||
) -> Result<Self, String> {
|
) -> Result<Box<Self>, InstantiationError> {
|
||||||
let mut sig_registry = instantiate_signatures(module);
|
let mut sig_registry = create_and_initialize_signatures(&module);
|
||||||
let mut memories = instantiate_memories(module, data_initializers)?;
|
let mut tables = create_tables(&module);
|
||||||
let mut tables = instantiate_tables(
|
let mut memories = create_memories(&module)?;
|
||||||
module,
|
|
||||||
finished_functions,
|
let mut vmctx_tables = tables
|
||||||
&vmctx_imports.functions,
|
.values_mut()
|
||||||
&mut sig_registry,
|
.map(Table::vmtable)
|
||||||
);
|
.collect::<PrimaryMap<DefinedTableIndex, _>>()
|
||||||
|
.into_boxed_slice();
|
||||||
|
|
||||||
let mut vmctx_memories = memories
|
let mut vmctx_memories = memories
|
||||||
.values_mut()
|
.values_mut()
|
||||||
@@ -69,13 +81,7 @@ impl Instance {
|
|||||||
.collect::<PrimaryMap<DefinedMemoryIndex, _>>()
|
.collect::<PrimaryMap<DefinedMemoryIndex, _>>()
|
||||||
.into_boxed_slice();
|
.into_boxed_slice();
|
||||||
|
|
||||||
let mut vmctx_globals = instantiate_globals(module);
|
let mut vmctx_globals = create_globals(&module);
|
||||||
|
|
||||||
let mut vmctx_tables = tables
|
|
||||||
.values_mut()
|
|
||||||
.map(Table::vmtable)
|
|
||||||
.collect::<PrimaryMap<DefinedTableIndex, _>>()
|
|
||||||
.into_boxed_slice();
|
|
||||||
|
|
||||||
let vmctx_imported_functions_ptr = vmctx_imports
|
let vmctx_imported_functions_ptr = vmctx_imports
|
||||||
.functions
|
.functions
|
||||||
@@ -90,19 +96,21 @@ impl Instance {
|
|||||||
.as_mut_ptr();
|
.as_mut_ptr();
|
||||||
let vmctx_imported_globals_ptr =
|
let vmctx_imported_globals_ptr =
|
||||||
vmctx_imports.globals.values_mut().into_slice().as_mut_ptr();
|
vmctx_imports.globals.values_mut().into_slice().as_mut_ptr();
|
||||||
|
let vmctx_tables_ptr = vmctx_tables.values_mut().into_slice().as_mut_ptr();
|
||||||
let vmctx_memories_ptr = vmctx_memories.values_mut().into_slice().as_mut_ptr();
|
let vmctx_memories_ptr = vmctx_memories.values_mut().into_slice().as_mut_ptr();
|
||||||
let vmctx_globals_ptr = vmctx_globals.values_mut().into_slice().as_mut_ptr();
|
let vmctx_globals_ptr = vmctx_globals.values_mut().into_slice().as_mut_ptr();
|
||||||
let vmctx_tables_ptr = vmctx_tables.values_mut().into_slice().as_mut_ptr();
|
|
||||||
let vmctx_shared_signatures_ptr = sig_registry.vmshared_signatures();
|
let vmctx_shared_signatures_ptr = sig_registry.vmshared_signatures();
|
||||||
|
|
||||||
Ok(Self {
|
let mut result = Box::new(Self {
|
||||||
|
module,
|
||||||
memories,
|
memories,
|
||||||
tables,
|
tables,
|
||||||
sig_registry,
|
sig_registry,
|
||||||
vmctx_imports,
|
vmctx_imports,
|
||||||
|
finished_functions,
|
||||||
|
vmctx_tables,
|
||||||
vmctx_memories,
|
vmctx_memories,
|
||||||
vmctx_globals,
|
vmctx_globals,
|
||||||
vmctx_tables,
|
|
||||||
vmctx: VMContext::new(
|
vmctx: VMContext::new(
|
||||||
vmctx_imported_functions_ptr,
|
vmctx_imported_functions_ptr,
|
||||||
vmctx_imported_tables_ptr,
|
vmctx_imported_tables_ptr,
|
||||||
@@ -113,7 +121,31 @@ impl Instance {
|
|||||||
vmctx_globals_ptr,
|
vmctx_globals_ptr,
|
||||||
vmctx_shared_signatures_ptr,
|
vmctx_shared_signatures_ptr,
|
||||||
),
|
),
|
||||||
})
|
});
|
||||||
|
|
||||||
|
// Check initializer bounds before initializing anything.
|
||||||
|
check_table_init_bounds(&mut *result)?;
|
||||||
|
check_memory_init_bounds(&mut *result, &data_initializers)?;
|
||||||
|
|
||||||
|
// Apply the initializers.
|
||||||
|
initialize_tables(&mut *result)?;
|
||||||
|
initialize_memories(&mut *result, data_initializers)?;
|
||||||
|
initialize_globals(&mut *result);
|
||||||
|
|
||||||
|
// Rather than writing inline assembly to jump to the code region, we use the fact that
|
||||||
|
// the Rust ABI for calling a function with no arguments and no return values matches the one
|
||||||
|
// of the generated code. Thanks to this, we can transmute the code region into a first-class
|
||||||
|
// Rust function and call it.
|
||||||
|
// Ensure that our signal handlers are ready for action.
|
||||||
|
// TODO: Move these calls out of `Instance`.
|
||||||
|
wasmtime_init_eager();
|
||||||
|
wasmtime_init_finish(result.vmctx_mut());
|
||||||
|
|
||||||
|
// The WebAssembly spec specifies that the start function is
|
||||||
|
// invoked automatically at instantiation time.
|
||||||
|
result.invoke_start_function()?;
|
||||||
|
|
||||||
|
Ok(result)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return a reference to the vmctx used by JIT code.
|
/// Return a reference to the vmctx used by JIT code.
|
||||||
@@ -121,11 +153,21 @@ impl Instance {
|
|||||||
&self.vmctx
|
&self.vmctx
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Return a raw pointer to the vmctx used by JIT code.
|
||||||
|
pub fn vmctx_ptr(&self) -> *const VMContext {
|
||||||
|
self.vmctx()
|
||||||
|
}
|
||||||
|
|
||||||
/// Return a mutable reference to the vmctx used by JIT code.
|
/// Return a mutable reference to the vmctx used by JIT code.
|
||||||
pub fn vmctx_mut(&mut self) -> &mut VMContext {
|
pub fn vmctx_mut(&mut self) -> &mut VMContext {
|
||||||
&mut self.vmctx
|
&mut self.vmctx
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Return a mutable raw pointer to the vmctx used by JIT code.
|
||||||
|
pub fn vmctx_mut_ptr(&mut self) -> *mut VMContext {
|
||||||
|
self.vmctx_mut()
|
||||||
|
}
|
||||||
|
|
||||||
/// Return the offset from the vmctx pointer to its containing Instance.
|
/// Return the offset from the vmctx pointer to its containing Instance.
|
||||||
pub(crate) fn vmctx_offset() -> isize {
|
pub(crate) fn vmctx_offset() -> isize {
|
||||||
offset_of!(Self, vmctx) as isize
|
offset_of!(Self, vmctx) as isize
|
||||||
@@ -166,11 +208,195 @@ impl Instance {
|
|||||||
|
|
||||||
/// Return the number of imported memories.
|
/// Return the number of imported memories.
|
||||||
pub(crate) fn num_imported_memories(&self) -> usize {
|
pub(crate) fn num_imported_memories(&self) -> usize {
|
||||||
self.vmctx_imports.functions.len()
|
self.vmctx_imports.memories.len()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Invoke the WebAssembly start function of the instance, if one is present.
|
||||||
|
fn invoke_start_function(&mut self) -> Result<(), InstantiationError> {
|
||||||
|
if let Some(start_index) = self.module.start_func {
|
||||||
|
let (callee_address, callee_vmctx) = match self.module.defined_func_index(start_index) {
|
||||||
|
Some(defined_start_index) => {
|
||||||
|
let body = self
|
||||||
|
.finished_functions
|
||||||
|
.get(defined_start_index)
|
||||||
|
.expect("start function index is out of bounds")
|
||||||
|
.clone();
|
||||||
|
(body, self.vmctx_mut() as *mut VMContext)
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
assert!(start_index.index() < self.module.imported_funcs.len());
|
||||||
|
let import = unsafe { self.vmctx.imported_function(start_index) };
|
||||||
|
(import.body, import.vmctx)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Make the call.
|
||||||
|
unsafe { wasmtime_call(callee_address, callee_vmctx) }
|
||||||
|
.map_err(InstantiationError::StartTrap)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Lookup an export with the given name.
|
||||||
|
pub fn lookup(&mut self, field: &str) -> Option<Export> {
|
||||||
|
if let Some(export) = self.module.exports.get(field) {
|
||||||
|
Some(match export {
|
||||||
|
wasmtime_environ::Export::Function(index) => {
|
||||||
|
let signature = self.module.signatures[self.module.functions[*index]].clone();
|
||||||
|
let (address, vmctx) =
|
||||||
|
if let Some(def_index) = self.module.defined_func_index(*index) {
|
||||||
|
(
|
||||||
|
self.finished_functions[def_index],
|
||||||
|
&mut self.vmctx as *mut VMContext,
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
let import = unsafe { self.vmctx.imported_function(*index) };
|
||||||
|
(import.body, import.vmctx)
|
||||||
|
};
|
||||||
|
Export::Function {
|
||||||
|
address,
|
||||||
|
signature,
|
||||||
|
vmctx,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
wasmtime_environ::Export::Table(index) => {
|
||||||
|
let (address, vmctx) = if let Some(def_index) =
|
||||||
|
self.module.defined_table_index(*index)
|
||||||
|
{
|
||||||
|
(
|
||||||
|
unsafe { self.vmctx.table_mut(def_index) } as *mut VMTableDefinition,
|
||||||
|
&mut self.vmctx as *mut VMContext,
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
let import = unsafe { self.vmctx.imported_table(*index) };
|
||||||
|
(import.from, import.vmctx)
|
||||||
|
};
|
||||||
|
Export::Table {
|
||||||
|
address,
|
||||||
|
vmctx,
|
||||||
|
table: self.module.table_plans[*index].clone(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
wasmtime_environ::Export::Memory(index) => {
|
||||||
|
let (address, vmctx) = if let Some(def_index) =
|
||||||
|
self.module.defined_memory_index(*index)
|
||||||
|
{
|
||||||
|
(
|
||||||
|
unsafe { self.vmctx.memory_mut(def_index) } as *mut VMMemoryDefinition,
|
||||||
|
&mut self.vmctx as *mut VMContext,
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
let import = unsafe { self.vmctx.imported_memory(*index) };
|
||||||
|
(import.from, import.vmctx)
|
||||||
|
};
|
||||||
|
Export::Memory {
|
||||||
|
address,
|
||||||
|
vmctx,
|
||||||
|
memory: self.module.memory_plans[*index].clone(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
wasmtime_environ::Export::Global(index) => Export::Global {
|
||||||
|
address: if let Some(def_index) = self.module.defined_global_index(*index) {
|
||||||
|
unsafe { self.vmctx.global_mut(def_index) }
|
||||||
|
} else {
|
||||||
|
unsafe { self.vmctx.imported_global(*index).from }
|
||||||
|
},
|
||||||
|
global: self.module.globals[*index].clone(),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Lookup an export with the given name. This takes an immutable reference,
|
||||||
|
/// and the result is an `Export` that can only be used to read, not write.
|
||||||
|
/// This requirement is not enforced in the type system, so this function is
|
||||||
|
/// unsafe.
|
||||||
|
pub unsafe fn lookup_immutable(&self, field: &str) -> Option<Export> {
|
||||||
|
let temporary_mut = &mut *(self as *const Instance as *mut Instance);
|
||||||
|
temporary_mut.lookup(field)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn instantiate_signatures(module: &Module) -> SignatureRegistry {
|
fn check_table_init_bounds(instance: &mut Instance) -> Result<(), InstantiationError> {
|
||||||
|
for init in &instance.module.table_elements {
|
||||||
|
// TODO: Refactor this.
|
||||||
|
let mut start = init.offset;
|
||||||
|
if let Some(base) = init.base {
|
||||||
|
let global = if let Some(def_index) = instance.module.defined_global_index(base) {
|
||||||
|
unsafe { instance.vmctx.global_mut(def_index) }
|
||||||
|
} else {
|
||||||
|
unsafe { instance.vmctx.imported_global(base).from }
|
||||||
|
};
|
||||||
|
start += unsafe { *(&*global).as_i32() } as u32 as usize;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Refactor this.
|
||||||
|
let slice = if let Some(defined_table_index) =
|
||||||
|
instance.module.defined_table_index(init.table_index)
|
||||||
|
{
|
||||||
|
instance.tables[defined_table_index].as_mut()
|
||||||
|
} else {
|
||||||
|
let import = &instance.vmctx_imports.tables[init.table_index];
|
||||||
|
let foreign_instance = unsafe { (&mut *(import).vmctx).instance() };
|
||||||
|
let foreign_table = unsafe { &mut *(import).from };
|
||||||
|
let foreign_index = foreign_instance.vmctx().table_index(foreign_table);
|
||||||
|
foreign_instance.tables[foreign_index].as_mut()
|
||||||
|
};
|
||||||
|
|
||||||
|
if slice.get_mut(start..start + init.elements.len()).is_none() {
|
||||||
|
return Err(InstantiationError::Link(
|
||||||
|
"elements segment does not fit".to_owned(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn check_memory_init_bounds(
|
||||||
|
instance: &mut Instance,
|
||||||
|
data_initializers: &[DataInitializer],
|
||||||
|
) -> Result<(), InstantiationError> {
|
||||||
|
for init in data_initializers {
|
||||||
|
// TODO: Refactor this.
|
||||||
|
let mut start = init.offset;
|
||||||
|
if let Some(base) = init.base {
|
||||||
|
let global = if let Some(def_index) = instance.module.defined_global_index(base) {
|
||||||
|
unsafe { instance.vmctx.global_mut(def_index) }
|
||||||
|
} else {
|
||||||
|
unsafe { instance.vmctx.imported_global(base).from }
|
||||||
|
};
|
||||||
|
start += unsafe { *(&*global).as_i32() } as u32 as usize;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Refactor this.
|
||||||
|
let memory = if let Some(defined_memory_index) =
|
||||||
|
instance.module.defined_memory_index(init.memory_index)
|
||||||
|
{
|
||||||
|
unsafe { instance.vmctx.memory(defined_memory_index) }
|
||||||
|
} else {
|
||||||
|
let import = &instance.vmctx_imports.memories[init.memory_index];
|
||||||
|
let foreign_instance = unsafe { (&mut *(import).vmctx).instance() };
|
||||||
|
let foreign_memory = unsafe { &mut *(import).from };
|
||||||
|
let foreign_index = foreign_instance.vmctx().memory_index(foreign_memory);
|
||||||
|
unsafe { foreign_instance.vmctx.memory(foreign_index) }
|
||||||
|
};
|
||||||
|
let mem_slice = unsafe { slice::from_raw_parts_mut(memory.base, memory.current_length) };
|
||||||
|
|
||||||
|
if mem_slice.get_mut(start..start + init.data.len()).is_none() {
|
||||||
|
return Err(InstantiationError::Link(
|
||||||
|
"data segment does not fit".to_owned(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_and_initialize_signatures(module: &Module) -> SignatureRegistry {
|
||||||
let mut sig_registry = SignatureRegistry::new();
|
let mut sig_registry = SignatureRegistry::new();
|
||||||
for (sig_index, sig) in module.signatures.iter() {
|
for (sig_index, sig) in module.signatures.iter() {
|
||||||
sig_registry.register(sig_index, sig);
|
sig_registry.register(sig_index, sig);
|
||||||
@@ -179,78 +405,169 @@ fn instantiate_signatures(module: &Module) -> SignatureRegistry {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Allocate memory for just the tables of the current module.
|
/// Allocate memory for just the tables of the current module.
|
||||||
fn instantiate_tables(
|
fn create_tables(module: &Module) -> BoxedSlice<DefinedTableIndex, Table> {
|
||||||
module: &Module,
|
let num_imports = module.imported_tables.len();
|
||||||
finished_functions: &BoxedSlice<DefinedFuncIndex, *const VMFunctionBody>,
|
|
||||||
imported_functions: &BoxedSlice<FuncIndex, *const VMFunctionBody>,
|
|
||||||
sig_registry: &mut SignatureRegistry,
|
|
||||||
) -> BoxedSlice<DefinedTableIndex, Table> {
|
|
||||||
let num_imports = module.imported_memories.len();
|
|
||||||
let mut tables: PrimaryMap<DefinedTableIndex, _> =
|
let mut tables: PrimaryMap<DefinedTableIndex, _> =
|
||||||
PrimaryMap::with_capacity(module.table_plans.len() - num_imports);
|
PrimaryMap::with_capacity(module.table_plans.len() - num_imports);
|
||||||
for table in &module.table_plans.values().as_slice()[num_imports..] {
|
for table in &module.table_plans.values().as_slice()[num_imports..] {
|
||||||
tables.push(Table::new(table));
|
tables.push(Table::new(table));
|
||||||
}
|
}
|
||||||
|
|
||||||
for init in &module.table_elements {
|
|
||||||
debug_assert!(init.base.is_none(), "globalvar base not supported yet");
|
|
||||||
let defined_table_index = module
|
|
||||||
.defined_table_index(init.table_index)
|
|
||||||
.expect("Initializers for imported tables not supported yet");
|
|
||||||
let slice = tables[defined_table_index].as_mut();
|
|
||||||
let subslice = &mut slice[init.offset..init.offset + init.elements.len()];
|
|
||||||
for (i, func_idx) in init.elements.iter().enumerate() {
|
|
||||||
let callee_sig = module.functions[*func_idx];
|
|
||||||
let func_ptr = if let Some(index) = module.defined_func_index(*func_idx) {
|
|
||||||
finished_functions[index]
|
|
||||||
} else {
|
|
||||||
imported_functions[*func_idx]
|
|
||||||
};
|
|
||||||
let type_index = sig_registry.lookup(callee_sig);
|
|
||||||
subslice[i] = VMCallerCheckedAnyfunc {
|
|
||||||
func_ptr,
|
|
||||||
type_index,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
tables.into_boxed_slice()
|
tables.into_boxed_slice()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Initialize the table memory from the provided initializers.
|
||||||
|
fn initialize_tables(instance: &mut Instance) -> Result<(), InstantiationError> {
|
||||||
|
let vmctx: *mut VMContext = instance.vmctx_mut();
|
||||||
|
for init in &instance.module.table_elements {
|
||||||
|
let mut start = init.offset;
|
||||||
|
if let Some(base) = init.base {
|
||||||
|
let global = if let Some(def_index) = instance.module.defined_global_index(base) {
|
||||||
|
unsafe { instance.vmctx.global_mut(def_index) }
|
||||||
|
} else {
|
||||||
|
unsafe { instance.vmctx.imported_global(base).from }
|
||||||
|
};
|
||||||
|
start += unsafe { *(&*global).as_i32() } as u32 as usize;
|
||||||
|
}
|
||||||
|
|
||||||
|
let slice = if let Some(defined_table_index) =
|
||||||
|
instance.module.defined_table_index(init.table_index)
|
||||||
|
{
|
||||||
|
instance.tables[defined_table_index].as_mut()
|
||||||
|
} else {
|
||||||
|
let import = &instance.vmctx_imports.tables[init.table_index];
|
||||||
|
let foreign_instance = unsafe { (&mut *(import).vmctx).instance() };
|
||||||
|
let foreign_table = unsafe { &mut *(import).from };
|
||||||
|
let foreign_index = foreign_instance.vmctx().table_index(foreign_table);
|
||||||
|
foreign_instance.tables[foreign_index].as_mut()
|
||||||
|
};
|
||||||
|
if let Some(subslice) = slice.get_mut(start..start + init.elements.len()) {
|
||||||
|
for (i, func_idx) in init.elements.iter().enumerate() {
|
||||||
|
let callee_sig = instance.module.functions[*func_idx];
|
||||||
|
let (callee_ptr, callee_vmctx) =
|
||||||
|
if let Some(index) = instance.module.defined_func_index(*func_idx) {
|
||||||
|
(instance.finished_functions[index], vmctx)
|
||||||
|
} else {
|
||||||
|
let imported_func = &instance.vmctx_imports.functions[*func_idx];
|
||||||
|
(imported_func.body, imported_func.vmctx)
|
||||||
|
};
|
||||||
|
let type_index = instance.sig_registry.lookup(callee_sig);
|
||||||
|
subslice[i] = VMCallerCheckedAnyfunc {
|
||||||
|
func_ptr: callee_ptr,
|
||||||
|
type_index,
|
||||||
|
vmctx: callee_vmctx,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return Err(InstantiationError::Link(
|
||||||
|
"elements segment does not fit".to_owned(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
/// Allocate memory for just the memories of the current module.
|
/// Allocate memory for just the memories of the current module.
|
||||||
fn instantiate_memories(
|
fn create_memories(
|
||||||
module: &Module,
|
module: &Module,
|
||||||
data_initializers: &[DataInitializer],
|
) -> Result<BoxedSlice<DefinedMemoryIndex, LinearMemory>, InstantiationError> {
|
||||||
) -> Result<BoxedSlice<DefinedMemoryIndex, LinearMemory>, String> {
|
|
||||||
let num_imports = module.imported_memories.len();
|
let num_imports = module.imported_memories.len();
|
||||||
let mut memories: PrimaryMap<DefinedMemoryIndex, _> =
|
let mut memories: PrimaryMap<DefinedMemoryIndex, _> =
|
||||||
PrimaryMap::with_capacity(module.memory_plans.len() - num_imports);
|
PrimaryMap::with_capacity(module.memory_plans.len() - num_imports);
|
||||||
for plan in &module.memory_plans.values().as_slice()[num_imports..] {
|
for plan in &module.memory_plans.values().as_slice()[num_imports..] {
|
||||||
memories.push(LinearMemory::new(&plan)?);
|
memories.push(LinearMemory::new(&plan).map_err(InstantiationError::Resource)?);
|
||||||
}
|
}
|
||||||
|
|
||||||
for init in data_initializers {
|
|
||||||
debug_assert!(init.base.is_none(), "globalvar base not supported yet");
|
|
||||||
let defined_memory_index = module
|
|
||||||
.defined_memory_index(init.memory_index)
|
|
||||||
.expect("Initializers for imported memories not supported yet");
|
|
||||||
let mem_mut = memories[defined_memory_index].as_mut();
|
|
||||||
let to_init = &mut mem_mut[init.offset..init.offset + init.data.len()];
|
|
||||||
to_init.copy_from_slice(init.data);
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(memories.into_boxed_slice())
|
Ok(memories.into_boxed_slice())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Initialize the table memory from the provided initializers.
|
||||||
|
fn initialize_memories(
|
||||||
|
instance: &mut Instance,
|
||||||
|
data_initializers: Vec<DataInitializer>,
|
||||||
|
) -> Result<(), InstantiationError> {
|
||||||
|
for init in data_initializers {
|
||||||
|
let mut start = init.offset;
|
||||||
|
if let Some(base) = init.base {
|
||||||
|
let global = if let Some(def_index) = instance.module.defined_global_index(base) {
|
||||||
|
unsafe { instance.vmctx.global_mut(def_index) }
|
||||||
|
} else {
|
||||||
|
unsafe { instance.vmctx.imported_global(base).from }
|
||||||
|
};
|
||||||
|
start += unsafe { *(&*global).as_i32() } as u32 as usize;
|
||||||
|
}
|
||||||
|
|
||||||
|
let memory = if let Some(defined_memory_index) =
|
||||||
|
instance.module.defined_memory_index(init.memory_index)
|
||||||
|
{
|
||||||
|
unsafe { instance.vmctx.memory(defined_memory_index) }
|
||||||
|
} else {
|
||||||
|
let import = &instance.vmctx_imports.memories[init.memory_index];
|
||||||
|
let foreign_instance = unsafe { (&mut *(import).vmctx).instance() };
|
||||||
|
let foreign_memory = unsafe { &mut *(import).from };
|
||||||
|
let foreign_index = foreign_instance.vmctx().memory_index(foreign_memory);
|
||||||
|
unsafe { foreign_instance.vmctx.memory(foreign_index) }
|
||||||
|
};
|
||||||
|
let mem_slice = unsafe { slice::from_raw_parts_mut(memory.base, memory.current_length) };
|
||||||
|
if let Some(to_init) = mem_slice.get_mut(start..start + init.data.len()) {
|
||||||
|
to_init.copy_from_slice(init.data);
|
||||||
|
} else {
|
||||||
|
return Err(InstantiationError::Link(
|
||||||
|
"data segment does not fit".to_owned(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
/// Allocate memory for just the globals of the current module,
|
/// Allocate memory for just the globals of the current module,
|
||||||
/// without any initializers applied yet.
|
/// with initializers applied.
|
||||||
fn instantiate_globals(module: &Module) -> BoxedSlice<DefinedGlobalIndex, VMGlobalDefinition> {
|
fn create_globals(module: &Module) -> BoxedSlice<DefinedGlobalIndex, VMGlobalDefinition> {
|
||||||
let num_imports = module.imported_globals.len();
|
let num_imports = module.imported_globals.len();
|
||||||
let mut vmctx_globals = PrimaryMap::with_capacity(module.globals.len() - num_imports);
|
let mut vmctx_globals = PrimaryMap::with_capacity(module.globals.len() - num_imports);
|
||||||
|
|
||||||
for global in &module.globals.values().as_slice()[num_imports..] {
|
for _ in &module.globals.values().as_slice()[num_imports..] {
|
||||||
vmctx_globals.push(VMGlobalDefinition::new(global));
|
vmctx_globals.push(VMGlobalDefinition::new());
|
||||||
}
|
}
|
||||||
|
|
||||||
vmctx_globals.into_boxed_slice()
|
vmctx_globals.into_boxed_slice()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn initialize_globals(instance: &mut Instance) {
|
||||||
|
let num_imports = instance.module.imported_globals.len();
|
||||||
|
for (index, global) in instance.module.globals.iter().skip(num_imports) {
|
||||||
|
let def_index = instance.module.defined_global_index(index).unwrap();
|
||||||
|
let to: *mut VMGlobalDefinition = unsafe { instance.vmctx.global_mut(def_index) };
|
||||||
|
match global.initializer {
|
||||||
|
GlobalInit::I32Const(x) => *unsafe { (*to).as_i32_mut() } = x,
|
||||||
|
GlobalInit::I64Const(x) => *unsafe { (*to).as_i64_mut() } = x,
|
||||||
|
GlobalInit::F32Const(x) => *unsafe { (*to).as_f32_bits_mut() } = x,
|
||||||
|
GlobalInit::F64Const(x) => *unsafe { (*to).as_f64_bits_mut() } = x,
|
||||||
|
GlobalInit::GetGlobal(x) => {
|
||||||
|
let from = if let Some(def_x) = instance.module.defined_global_index(x) {
|
||||||
|
unsafe { instance.vmctx.global_mut(def_x) }
|
||||||
|
} else {
|
||||||
|
unsafe { instance.vmctx.imported_global(x).from }
|
||||||
|
};
|
||||||
|
unsafe { *to = *from };
|
||||||
|
}
|
||||||
|
GlobalInit::Import => panic!("locally-defined global initialized as import"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// An error while instantiating a module.
|
||||||
|
#[derive(Fail, Debug)]
|
||||||
|
pub enum InstantiationError {
|
||||||
|
/// Insufficient resources available for execution.
|
||||||
|
#[fail(display = "Insufficient resources: {}", _0)]
|
||||||
|
Resource(String),
|
||||||
|
|
||||||
|
/// A wasm translation error occured.
|
||||||
|
#[fail(display = "Link error: {}", _0)]
|
||||||
|
Link(String),
|
||||||
|
|
||||||
|
/// A compilation error occured.
|
||||||
|
#[fail(display = "Trap occurred while invoking start function: {}", _0)]
|
||||||
|
StartTrap(String),
|
||||||
|
}
|
||||||
|
|||||||
@@ -39,7 +39,11 @@ extern crate libc;
|
|||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate memoffset;
|
extern crate memoffset;
|
||||||
extern crate cast;
|
extern crate cast;
|
||||||
|
extern crate failure;
|
||||||
|
#[macro_use]
|
||||||
|
extern crate failure_derive;
|
||||||
|
|
||||||
|
mod export;
|
||||||
mod imports;
|
mod imports;
|
||||||
mod instance;
|
mod instance;
|
||||||
mod memory;
|
mod memory;
|
||||||
@@ -52,14 +56,15 @@ mod vmcontext;
|
|||||||
|
|
||||||
pub mod libcalls;
|
pub mod libcalls;
|
||||||
|
|
||||||
|
pub use export::Export;
|
||||||
pub use imports::Imports;
|
pub use imports::Imports;
|
||||||
pub use instance::Instance;
|
pub use instance::{Instance, InstantiationError};
|
||||||
pub use mmap::Mmap;
|
pub use mmap::Mmap;
|
||||||
pub use signalhandlers::{wasmtime_init_eager, wasmtime_init_finish};
|
pub use signalhandlers::{wasmtime_init_eager, wasmtime_init_finish};
|
||||||
pub use traphandlers::wasmtime_call_trampoline;
|
pub use traphandlers::{wasmtime_call, wasmtime_call_trampoline};
|
||||||
pub use vmcontext::{
|
pub use vmcontext::{
|
||||||
VMContext, VMFunctionBody, VMGlobalDefinition, VMGlobalImport, VMMemoryDefinition,
|
VMContext, VMFunctionBody, VMFunctionImport, VMGlobalDefinition, VMGlobalImport,
|
||||||
VMMemoryImport, VMTableDefinition, VMTableImport,
|
VMMemoryDefinition, VMMemoryImport, VMTableDefinition, VMTableImport,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[cfg(not(feature = "std"))]
|
#[cfg(not(feature = "std"))]
|
||||||
|
|||||||
@@ -116,7 +116,7 @@ pub unsafe extern "C" fn wasmtime_imported_memory32_grow(
|
|||||||
);
|
);
|
||||||
|
|
||||||
let memory_index = MemoryIndex::from_u32(memory_index);
|
let memory_index = MemoryIndex::from_u32(memory_index);
|
||||||
let import = instance.vmctx_mut().imported_memory_mut(memory_index);
|
let import = instance.vmctx().imported_memory(memory_index);
|
||||||
let foreign_instance = (&mut *import.vmctx).instance();
|
let foreign_instance = (&mut *import.vmctx).instance();
|
||||||
let foreign_memory = &mut *import.from;
|
let foreign_memory = &mut *import.from;
|
||||||
let foreign_index = foreign_instance.vmctx().memory_index(foreign_memory);
|
let foreign_index = foreign_instance.vmctx().memory_index(foreign_memory);
|
||||||
@@ -148,7 +148,7 @@ pub unsafe extern "C" fn wasmtime_imported_memory32_size(
|
|||||||
);
|
);
|
||||||
|
|
||||||
let memory_index = MemoryIndex::from_u32(memory_index);
|
let memory_index = MemoryIndex::from_u32(memory_index);
|
||||||
let import = instance.vmctx_mut().imported_memory_mut(memory_index);
|
let import = instance.vmctx().imported_memory(memory_index);
|
||||||
let foreign_instance = (&mut *import.vmctx).instance();
|
let foreign_instance = (&mut *import.vmctx).instance();
|
||||||
let foreign_memory = &mut *import.from;
|
let foreign_memory = &mut *import.from;
|
||||||
let foreign_index = foreign_instance.vmctx().memory_index(foreign_memory);
|
let foreign_index = foreign_instance.vmctx().memory_index(foreign_memory);
|
||||||
|
|||||||
@@ -65,6 +65,7 @@ impl LinearMemory {
|
|||||||
let mmap = Mmap::with_size(request_bytes)?;
|
let mmap = Mmap::with_size(request_bytes)?;
|
||||||
|
|
||||||
// Make the unmapped and offset-guard pages inaccessible.
|
// Make the unmapped and offset-guard pages inaccessible.
|
||||||
|
if request_bytes != 0 {
|
||||||
unsafe {
|
unsafe {
|
||||||
region::protect(
|
region::protect(
|
||||||
mmap.as_ptr().add(mapped_bytes),
|
mmap.as_ptr().add(mapped_bytes),
|
||||||
@@ -73,6 +74,7 @@ impl LinearMemory {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
.expect("unable to make memory inaccessible");
|
.expect("unable to make memory inaccessible");
|
||||||
|
}
|
||||||
|
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
mmap,
|
mmap,
|
||||||
@@ -150,19 +152,7 @@ impl LinearMemory {
|
|||||||
pub fn vmmemory(&mut self) -> VMMemoryDefinition {
|
pub fn vmmemory(&mut self) -> VMMemoryDefinition {
|
||||||
VMMemoryDefinition {
|
VMMemoryDefinition {
|
||||||
base: self.mmap.as_mut_ptr(),
|
base: self.mmap.as_mut_ptr(),
|
||||||
current_length: self.mmap.len(),
|
current_length: self.current as usize * WASM_PAGE_SIZE as usize,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AsRef<[u8]> for LinearMemory {
|
|
||||||
fn as_ref(&self) -> &[u8] {
|
|
||||||
self.mmap.as_slice()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl AsMut<[u8]> for LinearMemory {
|
|
||||||
fn as_mut(&mut self) -> &mut [u8] {
|
|
||||||
self.mmap.as_mut_slice()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -34,6 +34,12 @@ impl Mmap {
|
|||||||
/// suitably sized and aligned for memory protection.
|
/// suitably sized and aligned for memory protection.
|
||||||
#[cfg(not(target_os = "windows"))]
|
#[cfg(not(target_os = "windows"))]
|
||||||
pub fn with_size(size: usize) -> Result<Self, String> {
|
pub fn with_size(size: usize) -> Result<Self, String> {
|
||||||
|
// Mmap may return EINVAL if the size is zero, so just
|
||||||
|
// special-case that.
|
||||||
|
if size == 0 {
|
||||||
|
return Ok(Self::new());
|
||||||
|
}
|
||||||
|
|
||||||
let page_size = region::page::size();
|
let page_size = region::page::size();
|
||||||
let alloc_size = round_up_to_page_size(size, page_size);
|
let alloc_size = round_up_to_page_size(size, page_size);
|
||||||
let ptr = unsafe {
|
let ptr = unsafe {
|
||||||
|
|||||||
@@ -107,3 +107,27 @@ pub unsafe extern "C" fn wasmtime_call_trampoline(
|
|||||||
Ok(())
|
Ok(())
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Call the wasm function pointed to by `callee`, which has no arguments or
|
||||||
|
/// return values.
|
||||||
|
#[no_mangle]
|
||||||
|
pub unsafe extern "C" fn wasmtime_call(
|
||||||
|
callee: *const VMFunctionBody,
|
||||||
|
vmctx: *mut VMContext,
|
||||||
|
) -> Result<(), String> {
|
||||||
|
// In case wasm code calls Rust that panics and unwinds past this point,
|
||||||
|
// ensure that JMP_BUFS is unwound to its incoming state.
|
||||||
|
let _guard = ScopeGuard::new();
|
||||||
|
|
||||||
|
let func: fn(*mut VMContext) = mem::transmute(callee);
|
||||||
|
|
||||||
|
JMP_BUFS.with(|bufs| {
|
||||||
|
let mut buf = mem::uninitialized();
|
||||||
|
if setjmp(&mut buf) != 0 {
|
||||||
|
return TRAP_DATA.with(|data| Err(format!("wasm trap at {:?}", data.get().pc)));
|
||||||
|
}
|
||||||
|
bufs.borrow_mut().push(buf);
|
||||||
|
func(vmctx);
|
||||||
|
Ok(())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|||||||
@@ -3,12 +3,47 @@
|
|||||||
|
|
||||||
use cranelift_entity::EntityRef;
|
use cranelift_entity::EntityRef;
|
||||||
use cranelift_wasm::{
|
use cranelift_wasm::{
|
||||||
DefinedGlobalIndex, DefinedMemoryIndex, DefinedTableIndex, FuncIndex, Global, GlobalIndex,
|
DefinedGlobalIndex, DefinedMemoryIndex, DefinedTableIndex, FuncIndex, GlobalIndex, MemoryIndex,
|
||||||
GlobalInit, MemoryIndex, TableIndex,
|
TableIndex,
|
||||||
};
|
};
|
||||||
use instance::Instance;
|
use instance::Instance;
|
||||||
use std::{mem, ptr, u32};
|
use std::{mem, ptr, u32};
|
||||||
|
|
||||||
|
/// An imported function.
|
||||||
|
#[derive(Debug, Copy, Clone)]
|
||||||
|
#[repr(C)]
|
||||||
|
pub struct VMFunctionImport {
|
||||||
|
/// A pointer to the imported function body.
|
||||||
|
pub body: *const VMFunctionBody,
|
||||||
|
|
||||||
|
/// A pointer to the VMContext that owns the function.
|
||||||
|
pub vmctx: *mut VMContext,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test_vmfunction_import {
|
||||||
|
use super::VMFunctionImport;
|
||||||
|
use std::mem::size_of;
|
||||||
|
use wasmtime_environ::VMOffsets;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn check_vmfunction_import_offsets() {
|
||||||
|
let offsets = VMOffsets::new(size_of::<*mut u8>() as u8);
|
||||||
|
assert_eq!(
|
||||||
|
size_of::<VMFunctionImport>(),
|
||||||
|
usize::from(offsets.size_of_vmfunction_import())
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
offset_of!(VMFunctionImport, body),
|
||||||
|
usize::from(offsets.vmfunction_import_body())
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
offset_of!(VMFunctionImport, vmctx),
|
||||||
|
usize::from(offsets.vmfunction_import_vmctx())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// A placeholder byte-sized type which is just used to provide some amount of type
|
/// A placeholder byte-sized type which is just used to provide some amount of type
|
||||||
/// safety when dealing with pointers to JIT-compiled function bodies. Note that it's
|
/// safety when dealing with pointers to JIT-compiled function bodies. Note that it's
|
||||||
/// deliberately not Copy, as we shouldn't be carelessly copying function body bytes
|
/// deliberately not Copy, as we shouldn't be carelessly copying function body bytes
|
||||||
@@ -244,17 +279,8 @@ mod test_vmglobal_definition {
|
|||||||
|
|
||||||
impl VMGlobalDefinition {
|
impl VMGlobalDefinition {
|
||||||
/// Construct a `VMGlobalDefinition`.
|
/// Construct a `VMGlobalDefinition`.
|
||||||
pub fn new(global: &Global) -> Self {
|
pub fn new() -> Self {
|
||||||
let mut result = Self { storage: [0; 8] };
|
Self { storage: [0; 8] }
|
||||||
match global.initializer {
|
|
||||||
GlobalInit::I32Const(x) => *unsafe { result.as_i32_mut() } = x,
|
|
||||||
GlobalInit::I64Const(x) => *unsafe { result.as_i64_mut() } = x,
|
|
||||||
GlobalInit::F32Const(x) => *unsafe { result.as_f32_bits_mut() } = x,
|
|
||||||
GlobalInit::F64Const(x) => *unsafe { result.as_f64_bits_mut() } = x,
|
|
||||||
GlobalInit::GetGlobal(_x) => unimplemented!("globals init with get_global"),
|
|
||||||
GlobalInit::Import => panic!("attempting to initialize imported global"),
|
|
||||||
}
|
|
||||||
result
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return a reference to the value as an i32.
|
/// Return a reference to the value as an i32.
|
||||||
@@ -366,6 +392,7 @@ impl VMSharedSignatureIndex {
|
|||||||
pub struct VMCallerCheckedAnyfunc {
|
pub struct VMCallerCheckedAnyfunc {
|
||||||
pub func_ptr: *const VMFunctionBody,
|
pub func_ptr: *const VMFunctionBody,
|
||||||
pub type_index: VMSharedSignatureIndex,
|
pub type_index: VMSharedSignatureIndex,
|
||||||
|
pub vmctx: *mut VMContext,
|
||||||
// If more elements are added here, remember to add offset_of tests below!
|
// If more elements are added here, remember to add offset_of tests below!
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -390,6 +417,10 @@ mod test_vmcaller_checked_anyfunc {
|
|||||||
offset_of!(VMCallerCheckedAnyfunc, type_index),
|
offset_of!(VMCallerCheckedAnyfunc, type_index),
|
||||||
usize::from(offsets.vmcaller_checked_anyfunc_type_index())
|
usize::from(offsets.vmcaller_checked_anyfunc_type_index())
|
||||||
);
|
);
|
||||||
|
assert_eq!(
|
||||||
|
offset_of!(VMCallerCheckedAnyfunc, vmctx),
|
||||||
|
usize::from(offsets.vmcaller_checked_anyfunc_vmctx())
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -398,6 +429,7 @@ impl Default for VMCallerCheckedAnyfunc {
|
|||||||
Self {
|
Self {
|
||||||
func_ptr: ptr::null_mut(),
|
func_ptr: ptr::null_mut(),
|
||||||
type_index: VMSharedSignatureIndex::new(u32::MAX),
|
type_index: VMSharedSignatureIndex::new(u32::MAX),
|
||||||
|
vmctx: ptr::null_mut(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -413,16 +445,16 @@ impl Default for VMCallerCheckedAnyfunc {
|
|||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
pub struct VMContext {
|
pub struct VMContext {
|
||||||
/// A pointer to an array of `*const VMFunctionBody` instances, indexed by `FuncIndex`.
|
/// A pointer to an array of `*const VMFunctionBody` instances, indexed by `FuncIndex`.
|
||||||
imported_functions: *const *const VMFunctionBody,
|
imported_functions: *const VMFunctionImport,
|
||||||
|
|
||||||
/// A pointer to an array of `VMTableImport` instances, indexed by `TableIndex`.
|
/// A pointer to an array of `VMTableImport` instances, indexed by `TableIndex`.
|
||||||
imported_tables: *mut VMTableImport,
|
imported_tables: *const VMTableImport,
|
||||||
|
|
||||||
/// A pointer to an array of `VMMemoryImport` instances, indexed by `MemoryIndex`.
|
/// A pointer to an array of `VMMemoryImport` instances, indexed by `MemoryIndex`.
|
||||||
imported_memories: *mut VMMemoryImport,
|
imported_memories: *const VMMemoryImport,
|
||||||
|
|
||||||
/// A pointer to an array of `VMGlobalImport` instances, indexed by `GlobalIndex`.
|
/// A pointer to an array of `VMGlobalImport` instances, indexed by `GlobalIndex`.
|
||||||
imported_globals: *mut VMGlobalImport,
|
imported_globals: *const VMGlobalImport,
|
||||||
|
|
||||||
/// A pointer to an array of locally-defined `VMTableDefinition` instances,
|
/// A pointer to an array of locally-defined `VMTableDefinition` instances,
|
||||||
/// indexed by `DefinedTableIndex`.
|
/// indexed by `DefinedTableIndex`.
|
||||||
@@ -473,10 +505,10 @@ mod test {
|
|||||||
impl VMContext {
|
impl VMContext {
|
||||||
/// Create a new `VMContext` instance.
|
/// Create a new `VMContext` instance.
|
||||||
pub fn new(
|
pub fn new(
|
||||||
imported_functions: *const *const VMFunctionBody,
|
imported_functions: *const VMFunctionImport,
|
||||||
imported_tables: *mut VMTableImport,
|
imported_tables: *const VMTableImport,
|
||||||
imported_memories: *mut VMMemoryImport,
|
imported_memories: *const VMMemoryImport,
|
||||||
imported_globals: *mut VMGlobalImport,
|
imported_globals: *const VMGlobalImport,
|
||||||
tables: *mut VMTableDefinition,
|
tables: *mut VMTableDefinition,
|
||||||
memories: *mut VMMemoryDefinition,
|
memories: *mut VMMemoryDefinition,
|
||||||
globals: *mut VMGlobalDefinition,
|
globals: *mut VMGlobalDefinition,
|
||||||
@@ -495,8 +527,8 @@ impl VMContext {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Return a reference to imported function `index`.
|
/// Return a reference to imported function `index`.
|
||||||
pub unsafe fn imported_function(&self, index: FuncIndex) -> *const VMFunctionBody {
|
pub unsafe fn imported_function(&self, index: FuncIndex) -> &VMFunctionImport {
|
||||||
*self.imported_functions.add(index.index())
|
&*self.imported_functions.add(index.index())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return a reference to imported table `index`.
|
/// Return a reference to imported table `index`.
|
||||||
@@ -504,31 +536,16 @@ impl VMContext {
|
|||||||
&*self.imported_tables.add(index.index())
|
&*self.imported_tables.add(index.index())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return a mutable reference to imported table `index`.
|
|
||||||
pub unsafe fn imported_table_mut(&mut self, index: TableIndex) -> &mut VMTableImport {
|
|
||||||
&mut *self.imported_tables.add(index.index())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Return a reference to imported memory `index`.
|
/// Return a reference to imported memory `index`.
|
||||||
pub unsafe fn imported_memory(&self, index: MemoryIndex) -> &VMMemoryImport {
|
pub unsafe fn imported_memory(&self, index: MemoryIndex) -> &VMMemoryImport {
|
||||||
&*self.imported_memories.add(index.index())
|
&*self.imported_memories.add(index.index())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return a mutable reference to imported memory `index`.
|
|
||||||
pub unsafe fn imported_memory_mut(&mut self, index: MemoryIndex) -> &mut VMMemoryImport {
|
|
||||||
&mut *self.imported_memories.add(index.index())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Return a reference to imported global `index`.
|
/// Return a reference to imported global `index`.
|
||||||
pub unsafe fn imported_global(&self, index: GlobalIndex) -> &VMGlobalImport {
|
pub unsafe fn imported_global(&self, index: GlobalIndex) -> &VMGlobalImport {
|
||||||
&*self.imported_globals.add(index.index())
|
&*self.imported_globals.add(index.index())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return a mutable reference to imported global `index`.
|
|
||||||
pub unsafe fn imported_global_mut(&mut self, index: GlobalIndex) -> &mut VMGlobalImport {
|
|
||||||
&mut *self.imported_globals.add(index.index())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Return a reference to locally-defined table `index`.
|
/// Return a reference to locally-defined table `index`.
|
||||||
pub unsafe fn table(&self, index: DefinedTableIndex) -> &VMTableDefinition {
|
pub unsafe fn table(&self, index: DefinedTableIndex) -> &VMTableDefinition {
|
||||||
&*self.tables.add(index.index())
|
&*self.tables.add(index.index())
|
||||||
@@ -565,6 +582,16 @@ impl VMContext {
|
|||||||
&mut *((self as *mut Self as *mut u8).offset(-Instance::vmctx_offset()) as *mut Instance)
|
&mut *((self as *mut Self as *mut u8).offset(-Instance::vmctx_offset()) as *mut Instance)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Return the table index for the given `VMTableDefinition`.
|
||||||
|
pub fn table_index(&self, table: &mut VMTableDefinition) -> DefinedTableIndex {
|
||||||
|
// TODO: Use `offset_from` once it stablizes.
|
||||||
|
let begin = self.tables;
|
||||||
|
let end: *mut VMTableDefinition = table;
|
||||||
|
DefinedTableIndex::new(
|
||||||
|
(end as usize - begin as usize) / mem::size_of::<VMTableDefinition>(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
/// Return the memory index for the given `VMMemoryDefinition`.
|
/// Return the memory index for the given `VMMemoryDefinition`.
|
||||||
pub fn memory_index(&self, memory: &mut VMMemoryDefinition) -> DefinedMemoryIndex {
|
pub fn memory_index(&self, memory: &mut VMMemoryDefinition) -> DefinedMemoryIndex {
|
||||||
// TODO: Use `offset_from` once it stablizes.
|
// TODO: Use `offset_from` once it stablizes.
|
||||||
|
|||||||
@@ -1,103 +0,0 @@
|
|||||||
use std::env;
|
|
||||||
use std::fs::{read_dir, File};
|
|
||||||
use std::io::{self, Write};
|
|
||||||
use std::path::{Path, PathBuf};
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
let out_dir =
|
|
||||||
PathBuf::from(env::var("OUT_DIR").expect("The OUT_DIR environment variable must be set"));
|
|
||||||
let mut out =
|
|
||||||
File::create(out_dir.join("run_wast_files.rs")).expect("error creating run_wast_files.rs");
|
|
||||||
|
|
||||||
test_directory(&mut out, "misc_testsuite").unwrap();
|
|
||||||
test_directory(&mut out, "spec_testsuite").unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
fn test_directory(out: &mut File, testsuite: &str) -> io::Result<()> {
|
|
||||||
let mut dir_entries: Vec<_> = read_dir(testsuite)
|
|
||||||
.unwrap()
|
|
||||||
.map(|r| r.unwrap())
|
|
||||||
.filter(|dir_entry| {
|
|
||||||
let p = dir_entry.path();
|
|
||||||
if let Some(ext) = p.extension() {
|
|
||||||
// Only look at wast files.
|
|
||||||
if ext == "wast" {
|
|
||||||
// Ignore files starting with `.`, which could be editor temporary files
|
|
||||||
if let Some(stem) = p.file_stem() {
|
|
||||||
if let Some(stemstr) = stem.to_str() {
|
|
||||||
if !stemstr.starts_with('.') {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
false
|
|
||||||
})
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
dir_entries.sort_by_key(|dir| dir.path());
|
|
||||||
|
|
||||||
writeln!(
|
|
||||||
out,
|
|
||||||
"mod {} {{",
|
|
||||||
Path::new(testsuite)
|
|
||||||
.file_stem()
|
|
||||||
.unwrap()
|
|
||||||
.to_str()
|
|
||||||
.unwrap()
|
|
||||||
.replace("-", "_")
|
|
||||||
)?;
|
|
||||||
writeln!(out, " use super::{{native_isa, WastContext, Path}};")?;
|
|
||||||
for dir_entry in dir_entries {
|
|
||||||
let path = dir_entry.path();
|
|
||||||
let stemstr = path
|
|
||||||
.file_stem()
|
|
||||||
.expect("file_stem")
|
|
||||||
.to_str()
|
|
||||||
.expect("to_str");
|
|
||||||
|
|
||||||
writeln!(out, " #[test]")?;
|
|
||||||
if ignore(testsuite, stemstr) {
|
|
||||||
writeln!(out, " #[ignore]")?;
|
|
||||||
}
|
|
||||||
writeln!(
|
|
||||||
out,
|
|
||||||
" fn {}() {{",
|
|
||||||
avoid_keywords(&stemstr.replace("-", "_"))
|
|
||||||
)?;
|
|
||||||
writeln!(out, " let mut wast_context = WastContext::new().expect(\"error constructing WastContext\");")?;
|
|
||||||
writeln!(
|
|
||||||
out,
|
|
||||||
" wast_context.run_file(&*native_isa(), Path::new(\"{}\")).expect(\"error running wast file: {}\");",
|
|
||||||
path.display(),
|
|
||||||
path.display()
|
|
||||||
)?;
|
|
||||||
writeln!(out, " }}")?;
|
|
||||||
writeln!(out)?;
|
|
||||||
}
|
|
||||||
writeln!(out, "}}")?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn avoid_keywords(name: &str) -> &str {
|
|
||||||
match name {
|
|
||||||
"if" => "if_",
|
|
||||||
"loop" => "loop_",
|
|
||||||
"type" => "type_",
|
|
||||||
"const" => "const_",
|
|
||||||
"return" => "return_",
|
|
||||||
other => other,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn ignore(testsuite: &str, name: &str) -> bool {
|
|
||||||
match testsuite {
|
|
||||||
"spec_testsuite" => match name {
|
|
||||||
// These are the remaining spec testsuite failures.
|
|
||||||
"data" | "elem" | "imports" | "linking" => true,
|
|
||||||
_ => false,
|
|
||||||
},
|
|
||||||
_ => false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,16 +1,14 @@
|
|||||||
use cranelift_codegen::ir::types;
|
use cranelift_codegen::ir::types;
|
||||||
use cranelift_codegen::{ir, isa};
|
use cranelift_codegen::{ir, isa};
|
||||||
use cranelift_entity::PrimaryMap;
|
use cranelift_entity::PrimaryMap;
|
||||||
use cranelift_wasm::{Global, GlobalInit, Memory, Table, TableElementType};
|
use cranelift_wasm::{DefinedFuncIndex, Global, GlobalInit, Memory, Table, TableElementType};
|
||||||
use std::ptr;
|
use std::rc::Rc;
|
||||||
use target_lexicon::HOST;
|
use target_lexicon::HOST;
|
||||||
use wasmtime_environ::{
|
use wasmtime_environ::{
|
||||||
translate_signature, MemoryPlan, MemoryStyle, Module, TablePlan, TableStyle,
|
translate_signature, Export, MemoryPlan, MemoryStyle, Module, TablePlan, TableStyle,
|
||||||
};
|
|
||||||
use wasmtime_execute::{Export, Resolver};
|
|
||||||
use wasmtime_runtime::{
|
|
||||||
Imports, Instance, VMFunctionBody, VMGlobalDefinition, VMMemoryDefinition, VMTableDefinition,
|
|
||||||
};
|
};
|
||||||
|
use wasmtime_execute::{ActionError, InstancePlus};
|
||||||
|
use wasmtime_runtime::{Imports, VMFunctionBody};
|
||||||
|
|
||||||
extern "C" fn spectest_print() {}
|
extern "C" fn spectest_print() {}
|
||||||
|
|
||||||
@@ -46,195 +44,181 @@ extern "C" fn spectest_print_f64_f64(x: f64, y: f64) {
|
|||||||
println!("{}: f64", y);
|
println!("{}: f64", y);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct SpecTest {
|
/// Return an instance implementing the "spectest" interface used in the
|
||||||
instance: Instance,
|
/// spec testsuite.
|
||||||
spectest_global_i32: VMGlobalDefinition,
|
pub fn instantiate_spectest() -> Result<InstancePlus, ActionError> {
|
||||||
spectest_global_f32: VMGlobalDefinition,
|
|
||||||
spectest_global_f64: VMGlobalDefinition,
|
|
||||||
spectest_table: VMTableDefinition,
|
|
||||||
spectest_memory: VMMemoryDefinition,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl SpecTest {
|
|
||||||
pub fn new() -> Result<Self, String> {
|
|
||||||
let finished_functions = PrimaryMap::new();
|
|
||||||
let imports = Imports::none();
|
|
||||||
let data_initializers = Vec::new();
|
|
||||||
Ok(Self {
|
|
||||||
instance: Instance::new(
|
|
||||||
&Module::new(),
|
|
||||||
&finished_functions.into_boxed_slice(),
|
|
||||||
imports,
|
|
||||||
&data_initializers,
|
|
||||||
)?,
|
|
||||||
spectest_global_i32: VMGlobalDefinition::new(&Global {
|
|
||||||
ty: types::I32,
|
|
||||||
mutability: true,
|
|
||||||
initializer: GlobalInit::I32Const(0),
|
|
||||||
}),
|
|
||||||
spectest_global_f32: VMGlobalDefinition::new(&Global {
|
|
||||||
ty: types::I32,
|
|
||||||
mutability: true,
|
|
||||||
initializer: GlobalInit::F32Const(0),
|
|
||||||
}),
|
|
||||||
spectest_global_f64: VMGlobalDefinition::new(&Global {
|
|
||||||
ty: types::I32,
|
|
||||||
mutability: true,
|
|
||||||
initializer: GlobalInit::F64Const(0),
|
|
||||||
}),
|
|
||||||
spectest_table: VMTableDefinition {
|
|
||||||
base: ptr::null_mut(),
|
|
||||||
current_elements: 0,
|
|
||||||
},
|
|
||||||
spectest_memory: VMMemoryDefinition {
|
|
||||||
base: ptr::null_mut(),
|
|
||||||
current_length: 0,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Resolver for SpecTest {
|
|
||||||
fn resolve(&mut self, module: &str, field: &str) -> Option<Export> {
|
|
||||||
let call_conv = isa::CallConv::triple_default(&HOST);
|
let call_conv = isa::CallConv::triple_default(&HOST);
|
||||||
let pointer_type = types::Type::triple_pointer_type(&HOST);
|
let pointer_type = types::Type::triple_pointer_type(&HOST);
|
||||||
match module {
|
let mut module = Module::new();
|
||||||
"spectest" => match field {
|
let mut finished_functions: PrimaryMap<DefinedFuncIndex, *const VMFunctionBody> =
|
||||||
"print" => Some(Export::function(
|
PrimaryMap::new();
|
||||||
spectest_print as *const VMFunctionBody,
|
|
||||||
translate_signature(
|
let sig = module.signatures.push(translate_signature(
|
||||||
ir::Signature {
|
ir::Signature {
|
||||||
params: vec![],
|
params: vec![],
|
||||||
returns: vec![],
|
returns: vec![],
|
||||||
call_conv,
|
call_conv,
|
||||||
},
|
},
|
||||||
pointer_type,
|
pointer_type,
|
||||||
),
|
));
|
||||||
)),
|
let func = module.functions.push(sig);
|
||||||
"print_i32" => Some(Export::function(
|
module
|
||||||
spectest_print_i32 as *const VMFunctionBody,
|
.exports
|
||||||
translate_signature(
|
.insert("print".to_owned(), Export::Function(func));
|
||||||
|
finished_functions.push(spectest_print as *const VMFunctionBody);
|
||||||
|
|
||||||
|
let sig = module.signatures.push(translate_signature(
|
||||||
ir::Signature {
|
ir::Signature {
|
||||||
params: vec![ir::AbiParam::new(types::I32)],
|
params: vec![ir::AbiParam::new(types::I32)],
|
||||||
returns: vec![],
|
returns: vec![],
|
||||||
call_conv,
|
call_conv,
|
||||||
},
|
},
|
||||||
pointer_type,
|
pointer_type,
|
||||||
),
|
));
|
||||||
)),
|
let func = module.functions.push(sig);
|
||||||
"print_i64" => Some(Export::function(
|
module
|
||||||
spectest_print_i64 as *const VMFunctionBody,
|
.exports
|
||||||
translate_signature(
|
.insert("print_i32".to_owned(), Export::Function(func));
|
||||||
|
finished_functions.push(spectest_print_i32 as *const VMFunctionBody);
|
||||||
|
|
||||||
|
let sig = module.signatures.push(translate_signature(
|
||||||
ir::Signature {
|
ir::Signature {
|
||||||
params: vec![ir::AbiParam::new(types::I64)],
|
params: vec![ir::AbiParam::new(types::I64)],
|
||||||
returns: vec![],
|
returns: vec![],
|
||||||
call_conv,
|
call_conv,
|
||||||
},
|
},
|
||||||
pointer_type,
|
pointer_type,
|
||||||
),
|
));
|
||||||
)),
|
let func = module.functions.push(sig);
|
||||||
"print_f32" => Some(Export::function(
|
module
|
||||||
spectest_print_f32 as *const VMFunctionBody,
|
.exports
|
||||||
translate_signature(
|
.insert("print_i64".to_owned(), Export::Function(func));
|
||||||
|
finished_functions.push(spectest_print_i64 as *const VMFunctionBody);
|
||||||
|
|
||||||
|
let sig = module.signatures.push(translate_signature(
|
||||||
ir::Signature {
|
ir::Signature {
|
||||||
params: vec![ir::AbiParam::new(types::F32)],
|
params: vec![ir::AbiParam::new(types::F32)],
|
||||||
returns: vec![],
|
returns: vec![],
|
||||||
call_conv,
|
call_conv,
|
||||||
},
|
},
|
||||||
pointer_type,
|
pointer_type,
|
||||||
),
|
));
|
||||||
)),
|
let func = module.functions.push(sig);
|
||||||
"print_f64" => Some(Export::function(
|
module
|
||||||
spectest_print_f64 as *const VMFunctionBody,
|
.exports
|
||||||
translate_signature(
|
.insert("print_f32".to_owned(), Export::Function(func));
|
||||||
|
finished_functions.push(spectest_print_f32 as *const VMFunctionBody);
|
||||||
|
|
||||||
|
let sig = module.signatures.push(translate_signature(
|
||||||
ir::Signature {
|
ir::Signature {
|
||||||
params: vec![ir::AbiParam::new(types::F64)],
|
params: vec![ir::AbiParam::new(types::F64)],
|
||||||
returns: vec![],
|
returns: vec![],
|
||||||
call_conv,
|
call_conv,
|
||||||
},
|
},
|
||||||
pointer_type,
|
pointer_type,
|
||||||
),
|
));
|
||||||
)),
|
let func = module.functions.push(sig);
|
||||||
"print_i32_f32" => Some(Export::function(
|
module
|
||||||
spectest_print_i32_f32 as *const VMFunctionBody,
|
.exports
|
||||||
translate_signature(
|
.insert("print_f64".to_owned(), Export::Function(func));
|
||||||
|
finished_functions.push(spectest_print_f64 as *const VMFunctionBody);
|
||||||
|
|
||||||
|
let sig = module.signatures.push(translate_signature(
|
||||||
ir::Signature {
|
ir::Signature {
|
||||||
params: vec![
|
params: vec![ir::AbiParam::new(types::I32), ir::AbiParam::new(types::F32)],
|
||||||
ir::AbiParam::new(types::I32),
|
|
||||||
ir::AbiParam::new(types::F32),
|
|
||||||
],
|
|
||||||
returns: vec![],
|
returns: vec![],
|
||||||
call_conv,
|
call_conv,
|
||||||
},
|
},
|
||||||
pointer_type,
|
pointer_type,
|
||||||
),
|
));
|
||||||
)),
|
let func = module.functions.push(sig);
|
||||||
"print_f64_f64" => Some(Export::function(
|
module
|
||||||
spectest_print_f64_f64 as *const VMFunctionBody,
|
.exports
|
||||||
translate_signature(
|
.insert("print_i32_f32".to_owned(), Export::Function(func));
|
||||||
|
finished_functions.push(spectest_print_i32_f32 as *const VMFunctionBody);
|
||||||
|
|
||||||
|
let sig = module.signatures.push(translate_signature(
|
||||||
ir::Signature {
|
ir::Signature {
|
||||||
params: vec![
|
params: vec![ir::AbiParam::new(types::F64), ir::AbiParam::new(types::F64)],
|
||||||
ir::AbiParam::new(types::F64),
|
|
||||||
ir::AbiParam::new(types::F64),
|
|
||||||
],
|
|
||||||
returns: vec![],
|
returns: vec![],
|
||||||
call_conv,
|
call_conv,
|
||||||
},
|
},
|
||||||
pointer_type,
|
pointer_type,
|
||||||
),
|
));
|
||||||
)),
|
let func = module.functions.push(sig);
|
||||||
"global_i32" => Some(Export::global(
|
module
|
||||||
&mut self.spectest_global_i32,
|
.exports
|
||||||
Global {
|
.insert("print_f64_f64".to_owned(), Export::Function(func));
|
||||||
ty: ir::types::I32,
|
finished_functions.push(spectest_print_f64_f64 as *const VMFunctionBody);
|
||||||
|
|
||||||
|
let global = module.globals.push(Global {
|
||||||
|
ty: types::I32,
|
||||||
mutability: false,
|
mutability: false,
|
||||||
initializer: GlobalInit::I32Const(0),
|
initializer: GlobalInit::I32Const(666),
|
||||||
},
|
});
|
||||||
)),
|
module
|
||||||
"global_f32" => Some(Export::global(
|
.exports
|
||||||
&mut self.spectest_global_f32,
|
.insert("global_i32".to_owned(), Export::Global(global));
|
||||||
Global {
|
|
||||||
ty: ir::types::F32,
|
let global = module.globals.push(Global {
|
||||||
|
ty: types::I64,
|
||||||
mutability: false,
|
mutability: false,
|
||||||
initializer: GlobalInit::F32Const(0),
|
initializer: GlobalInit::I64Const(666),
|
||||||
},
|
});
|
||||||
)),
|
module
|
||||||
"global_f64" => Some(Export::global(
|
.exports
|
||||||
&mut self.spectest_global_f64,
|
.insert("global_i64".to_owned(), Export::Global(global));
|
||||||
Global {
|
|
||||||
ty: ir::types::F64,
|
let global = module.globals.push(Global {
|
||||||
|
ty: types::F32,
|
||||||
mutability: false,
|
mutability: false,
|
||||||
initializer: GlobalInit::F64Const(0),
|
initializer: GlobalInit::F32Const(0x44268000),
|
||||||
},
|
});
|
||||||
)),
|
module
|
||||||
"table" => Some(Export::table(
|
.exports
|
||||||
&mut self.spectest_table,
|
.insert("global_f32".to_owned(), Export::Global(global));
|
||||||
self.instance.vmctx_mut(),
|
|
||||||
TablePlan {
|
let global = module.globals.push(Global {
|
||||||
|
ty: types::F64,
|
||||||
|
mutability: false,
|
||||||
|
initializer: GlobalInit::F64Const(0x4084d00000000000),
|
||||||
|
});
|
||||||
|
module
|
||||||
|
.exports
|
||||||
|
.insert("global_f64".to_owned(), Export::Global(global));
|
||||||
|
|
||||||
|
let table = module.table_plans.push(TablePlan {
|
||||||
table: Table {
|
table: Table {
|
||||||
ty: TableElementType::Func,
|
ty: TableElementType::Func,
|
||||||
minimum: 0,
|
minimum: 10,
|
||||||
maximum: None,
|
maximum: Some(20),
|
||||||
},
|
},
|
||||||
style: TableStyle::CallerChecksSignature,
|
style: TableStyle::CallerChecksSignature,
|
||||||
},
|
});
|
||||||
)),
|
module
|
||||||
"memory" => Some(Export::memory(
|
.exports
|
||||||
&mut self.spectest_memory,
|
.insert("table".to_owned(), Export::Table(table));
|
||||||
self.instance.vmctx_mut(),
|
|
||||||
MemoryPlan {
|
let memory = module.memory_plans.push(MemoryPlan {
|
||||||
memory: Memory {
|
memory: Memory {
|
||||||
minimum: 0,
|
minimum: 1,
|
||||||
maximum: None,
|
maximum: Some(2),
|
||||||
shared: false,
|
shared: false,
|
||||||
},
|
},
|
||||||
style: MemoryStyle::Dynamic,
|
style: MemoryStyle::Static { bound: 65536 },
|
||||||
offset_guard_size: 0,
|
offset_guard_size: 0x80000000,
|
||||||
},
|
});
|
||||||
)),
|
module
|
||||||
_ => None,
|
.exports
|
||||||
},
|
.insert("memory".to_owned(), Export::Memory(memory));
|
||||||
_ => None,
|
|
||||||
}
|
let imports = Imports::none();
|
||||||
}
|
let data_initializers = Vec::new();
|
||||||
|
|
||||||
|
InstancePlus::with_parts(
|
||||||
|
Rc::new(module),
|
||||||
|
finished_functions.into_boxed_slice(),
|
||||||
|
imports,
|
||||||
|
data_initializers,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,13 @@
|
|||||||
use cranelift_codegen::isa;
|
use cranelift_codegen::isa;
|
||||||
use cranelift_entity::PrimaryMap;
|
use cranelift_entity::PrimaryMap;
|
||||||
use spectest::SpecTest;
|
use spectest::instantiate_spectest;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::io::Read;
|
use std::io::Read;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use std::{fmt, fs, io, str};
|
use std::{fmt, fs, io, str};
|
||||||
use wabt::script::{Action, Command, CommandKind, ModuleBinary, ScriptParser, Value};
|
use wabt::script::{Action, Command, CommandKind, ModuleBinary, ScriptParser, Value};
|
||||||
use wasmtime_execute::{ActionError, ActionOutcome, Code, InstanceWorld, RuntimeValue};
|
use wasmtime_execute::{ActionError, ActionOutcome, InstancePlus, JITCode, Resolver, RuntimeValue};
|
||||||
|
use wasmtime_runtime::Export;
|
||||||
|
|
||||||
/// Translate from a script::Value to a RuntimeValue.
|
/// Translate from a script::Value to a RuntimeValue.
|
||||||
fn runtime_value(v: Value) -> RuntimeValue {
|
fn runtime_value(v: Value) -> RuntimeValue {
|
||||||
@@ -72,45 +73,70 @@ pub struct WastFileError {
|
|||||||
error: WastError,
|
error: WastError,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// An opaque reference to an `InstanceWorld`.
|
/// An opaque reference to an `InstancePlus`.
|
||||||
#[derive(Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
|
#[derive(Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
|
||||||
pub struct WorldIndex(u32);
|
pub struct InstancePlusIndex(u32);
|
||||||
entity_impl!(WorldIndex, "world");
|
entity_impl!(InstancePlusIndex, "instance");
|
||||||
|
|
||||||
|
struct WasmNamespace {
|
||||||
|
names: HashMap<String, InstancePlusIndex>,
|
||||||
|
instances: PrimaryMap<InstancePlusIndex, InstancePlus>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl WasmNamespace {
|
||||||
|
fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
names: HashMap::new(),
|
||||||
|
instances: PrimaryMap::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Resolver for WasmNamespace {
|
||||||
|
fn resolve(&mut self, module: &str, field: &str) -> Option<Export> {
|
||||||
|
if let Some(index) = self.names.get(module) {
|
||||||
|
self.instances[*index].instance.lookup(field)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// The wast test script language allows modules to be defined and actions
|
/// The wast test script language allows modules to be defined and actions
|
||||||
/// to be performed on them.
|
/// to be performed on them.
|
||||||
pub struct WastContext {
|
pub struct WastContext {
|
||||||
/// A namespace of wasm modules, keyed by an optional name.
|
/// A namespace of wasm modules, keyed by an optional name.
|
||||||
worlds: PrimaryMap<WorldIndex, InstanceWorld>,
|
current: Option<InstancePlusIndex>,
|
||||||
current: Option<WorldIndex>,
|
namespace: WasmNamespace,
|
||||||
namespace: HashMap<String, WorldIndex>,
|
jit_code: JITCode,
|
||||||
code: Code,
|
|
||||||
spectest: SpecTest,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl WastContext {
|
impl WastContext {
|
||||||
/// Construct a new instance of `WastContext`.
|
/// Construct a new instance of `WastContext`.
|
||||||
pub fn new() -> Result<Self, String> {
|
pub fn new() -> Self {
|
||||||
Ok(Self {
|
Self {
|
||||||
worlds: PrimaryMap::new(),
|
|
||||||
current: None,
|
current: None,
|
||||||
namespace: HashMap::new(),
|
namespace: WasmNamespace::new(),
|
||||||
code: Code::new(),
|
jit_code: JITCode::new(),
|
||||||
spectest: SpecTest::new()?,
|
}
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn instantiate(
|
fn instantiate(
|
||||||
&mut self,
|
&mut self,
|
||||||
isa: &isa::TargetIsa,
|
isa: &isa::TargetIsa,
|
||||||
module: ModuleBinary,
|
module: ModuleBinary,
|
||||||
) -> Result<InstanceWorld, ActionError> {
|
) -> Result<InstancePlus, ActionError> {
|
||||||
InstanceWorld::new(&mut self.code, isa, &module.into_vec(), &mut self.spectest)
|
InstancePlus::new(
|
||||||
|
&mut self.jit_code,
|
||||||
|
isa,
|
||||||
|
&module.into_vec(),
|
||||||
|
&mut self.namespace,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_world(&mut self, module: &Option<String>) -> Result<WorldIndex, WastError> {
|
fn get_instance(&mut self, module: &Option<String>) -> Result<InstancePlusIndex, WastError> {
|
||||||
let index = *if let Some(name) = module {
|
let index = *if let Some(name) = module {
|
||||||
self.namespace.get_mut(name).ok_or_else(|| {
|
self.namespace.names.get_mut(name).ok_or_else(|| {
|
||||||
WastError::Module(UnknownModule {
|
WastError::Module(UnknownModule {
|
||||||
module: Some(name.to_owned()),
|
module: Some(name.to_owned()),
|
||||||
})
|
})
|
||||||
@@ -124,6 +150,14 @@ impl WastContext {
|
|||||||
Ok(index)
|
Ok(index)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Register "spectest" which is used by the spec testsuite.
|
||||||
|
pub fn register_spectest(&mut self) -> Result<(), ActionError> {
|
||||||
|
let instance = instantiate_spectest()?;
|
||||||
|
let index = self.namespace.instances.push(instance);
|
||||||
|
self.register("spectest".to_owned(), index);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
/// Define a module and register it.
|
/// Define a module and register it.
|
||||||
pub fn module(
|
pub fn module(
|
||||||
&mut self,
|
&mut self,
|
||||||
@@ -131,21 +165,18 @@ impl WastContext {
|
|||||||
name: Option<String>,
|
name: Option<String>,
|
||||||
module: ModuleBinary,
|
module: ModuleBinary,
|
||||||
) -> Result<(), ActionError> {
|
) -> Result<(), ActionError> {
|
||||||
let world = self.instantiate(isa, module)?;
|
let instance = self.instantiate(isa, module)?;
|
||||||
let index = if let Some(name) = name {
|
let index = self.namespace.instances.push(instance);
|
||||||
self.register(name, world)
|
if let Some(name) = name {
|
||||||
} else {
|
self.register(name, index);
|
||||||
self.worlds.push(world)
|
}
|
||||||
};
|
|
||||||
self.current = Some(index);
|
self.current = Some(index);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Register a module to make it available for performing actions.
|
/// Register a module to make it available for performing actions.
|
||||||
pub fn register(&mut self, name: String, world: InstanceWorld) -> WorldIndex {
|
pub fn register(&mut self, name: String, index: InstancePlusIndex) {
|
||||||
let index = self.worlds.push(world);
|
self.namespace.names.insert(name, index);
|
||||||
self.namespace.insert(name, index);
|
|
||||||
index
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Invoke an exported function from a defined module.
|
/// Invoke an exported function from a defined module.
|
||||||
@@ -160,16 +191,18 @@ impl WastContext {
|
|||||||
for arg in args {
|
for arg in args {
|
||||||
value_args.push(runtime_value(*arg));
|
value_args.push(runtime_value(*arg));
|
||||||
}
|
}
|
||||||
let index = self.get_world(&module)?;
|
let index = self.get_instance(&module)?;
|
||||||
self.worlds[index]
|
self.namespace.instances[index]
|
||||||
.invoke(&mut self.code, isa, &field, &value_args)
|
.invoke(&mut self.jit_code, isa, &field, &value_args)
|
||||||
.map_err(WastError::Action)
|
.map_err(WastError::Action)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get the value of an exported global from a defined module.
|
/// Get the value of an exported global from a defined module.
|
||||||
pub fn get(&mut self, module: Option<String>, field: &str) -> Result<RuntimeValue, WastError> {
|
pub fn get(&mut self, module: Option<String>, field: &str) -> Result<RuntimeValue, WastError> {
|
||||||
let index = self.get_world(&module)?;
|
let index = self.get_instance(&module)?;
|
||||||
self.worlds[index].get(&field).map_err(WastError::Action)
|
self.namespace.instances[index]
|
||||||
|
.get(&field)
|
||||||
|
.map_err(WastError::Action)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn perform_action(
|
fn perform_action(
|
||||||
@@ -211,11 +244,13 @@ impl WastContext {
|
|||||||
error: WastError::Action(error),
|
error: WastError::Action(error),
|
||||||
})?;
|
})?;
|
||||||
}
|
}
|
||||||
CommandKind::Register {
|
CommandKind::Register { name, as_name } => {
|
||||||
name: _name,
|
let index = self.get_instance(&name).map_err(|error| WastFileError {
|
||||||
as_name: _as_name,
|
filename: filename.to_string(),
|
||||||
} => {
|
line,
|
||||||
println!("{}:{}: TODO: Implement register", filename, line);
|
error,
|
||||||
|
})?;
|
||||||
|
self.register(as_name, index);
|
||||||
}
|
}
|
||||||
CommandKind::PerformAction(action) => match self
|
CommandKind::PerformAction(action) => match self
|
||||||
.perform_action(isa, action)
|
.perform_action(isa, action)
|
||||||
|
|||||||
@@ -31,9 +31,7 @@
|
|||||||
)]
|
)]
|
||||||
|
|
||||||
extern crate cranelift_codegen;
|
extern crate cranelift_codegen;
|
||||||
extern crate cranelift_entity;
|
|
||||||
extern crate cranelift_native;
|
extern crate cranelift_native;
|
||||||
extern crate cranelift_wasm;
|
|
||||||
extern crate docopt;
|
extern crate docopt;
|
||||||
extern crate wasmtime_execute;
|
extern crate wasmtime_execute;
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
@@ -45,18 +43,15 @@ extern crate wabt;
|
|||||||
use cranelift_codegen::isa::TargetIsa;
|
use cranelift_codegen::isa::TargetIsa;
|
||||||
use cranelift_codegen::settings;
|
use cranelift_codegen::settings;
|
||||||
use cranelift_codegen::settings::Configurable;
|
use cranelift_codegen::settings::Configurable;
|
||||||
use cranelift_entity::EntityRef;
|
|
||||||
use cranelift_wasm::MemoryIndex;
|
|
||||||
use docopt::Docopt;
|
use docopt::Docopt;
|
||||||
use std::error::Error;
|
use std::error::Error;
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
use std::io;
|
use std::io;
|
||||||
use std::io::prelude::*;
|
use std::io::prelude::*;
|
||||||
use std::io::stdout;
|
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use std::process::exit;
|
use std::process::exit;
|
||||||
use wasmtime_execute::{ActionOutcome, Code, InstanceWorld, NullResolver};
|
use wasmtime_execute::{ActionOutcome, InstancePlus, JITCode, NullResolver};
|
||||||
|
|
||||||
static LOG_FILENAME_PREFIX: &str = "cranelift.dbg.";
|
static LOG_FILENAME_PREFIX: &str = "cranelift.dbg.";
|
||||||
|
|
||||||
@@ -68,14 +63,13 @@ including calling the start function if one is present. Additional functions
|
|||||||
given with --invoke are then called.
|
given with --invoke are then called.
|
||||||
|
|
||||||
Usage:
|
Usage:
|
||||||
wasmtime [-omd] <file>...
|
wasmtime [-od] <file>...
|
||||||
wasmtime [-omd] <file>... --invoke=<fn>
|
wasmtime [-od] <file>... --invoke=<fn>
|
||||||
wasmtime --help | --version
|
wasmtime --help | --version
|
||||||
|
|
||||||
Options:
|
Options:
|
||||||
--invoke=<fn> name of function to run
|
--invoke=<fn> name of function to run
|
||||||
-o, --optimize runs optimization passes on the translated functions
|
-o, --optimize runs optimization passes on the translated functions
|
||||||
-m, --memory interactive memory inspector after execution
|
|
||||||
-d, --debug enable debug output on stderr/stdout
|
-d, --debug enable debug output on stderr/stdout
|
||||||
-h, --help print this help message
|
-h, --help print this help message
|
||||||
--version print the Cranelift version
|
--version print the Cranelift version
|
||||||
@@ -84,7 +78,6 @@ Options:
|
|||||||
#[derive(Deserialize, Debug, Clone)]
|
#[derive(Deserialize, Debug, Clone)]
|
||||||
struct Args {
|
struct Args {
|
||||||
arg_file: Vec<String>,
|
arg_file: Vec<String>,
|
||||||
flag_memory: bool,
|
|
||||||
flag_optimize: bool,
|
flag_optimize: bool,
|
||||||
flag_debug: bool,
|
flag_debug: bool,
|
||||||
flag_invoke: Option<String>,
|
flag_invoke: Option<String>,
|
||||||
@@ -150,13 +143,13 @@ fn handle_module(args: &Args, path: &Path, isa: &TargetIsa) -> Result<(), String
|
|||||||
data = wabt::wat2wasm(data).map_err(|err| String::from(err.description()))?;
|
data = wabt::wat2wasm(data).map_err(|err| String::from(err.description()))?;
|
||||||
}
|
}
|
||||||
let mut resolver = NullResolver {};
|
let mut resolver = NullResolver {};
|
||||||
let mut code = Code::new();
|
let mut jit_code = JITCode::new();
|
||||||
let mut world =
|
let mut instance_plus =
|
||||||
InstanceWorld::new(&mut code, isa, &data, &mut resolver).map_err(|e| e.to_string())?;
|
InstancePlus::new(&mut jit_code, isa, &data, &mut resolver).map_err(|e| e.to_string())?;
|
||||||
|
|
||||||
if let Some(ref f) = args.flag_invoke {
|
if let Some(ref f) = args.flag_invoke {
|
||||||
match world
|
match instance_plus
|
||||||
.invoke(&mut code, isa, &f, &[])
|
.invoke(&mut jit_code, isa, &f, &[])
|
||||||
.map_err(|e| e.to_string())?
|
.map_err(|e| e.to_string())?
|
||||||
{
|
{
|
||||||
ActionOutcome::Returned { .. } => {}
|
ActionOutcome::Returned { .. } => {}
|
||||||
@@ -166,42 +159,6 @@ fn handle_module(args: &Args, path: &Path, isa: &TargetIsa) -> Result<(), String
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if args.flag_memory {
|
|
||||||
let mut input = String::new();
|
|
||||||
println!("Inspecting memory");
|
|
||||||
println!("Type 'quit' to exit.");
|
|
||||||
loop {
|
|
||||||
input.clear();
|
|
||||||
print!("Memory index, offset, length (e.g. 0,0,4): ");
|
|
||||||
let _ = stdout().flush();
|
|
||||||
match io::stdin().read_line(&mut input) {
|
|
||||||
Ok(_) => {
|
|
||||||
input.pop();
|
|
||||||
if input == "quit" {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
let split: Vec<&str> = input.split(',').collect();
|
|
||||||
if split.len() != 3 {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
let memory = world
|
|
||||||
.inspect_memory(
|
|
||||||
MemoryIndex::new(str::parse(split[0]).unwrap()),
|
|
||||||
str::parse(split[1]).unwrap(),
|
|
||||||
str::parse(split[2]).unwrap(),
|
|
||||||
)
|
|
||||||
.map_err(|e| e.to_string())?;
|
|
||||||
let mut s = memory.iter().fold(String::from("#"), |mut acc, byte| {
|
|
||||||
acc.push_str(format!("{:02x}_", byte).as_str());
|
|
||||||
acc
|
|
||||||
});
|
|
||||||
s.pop();
|
|
||||||
println!("{}", s);
|
|
||||||
}
|
|
||||||
Err(error) => return Err(String::from(error.description())),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -211,7 +168,7 @@ mod tests {
|
|||||||
use cranelift_codegen::settings::Configurable;
|
use cranelift_codegen::settings::Configurable;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use wabt;
|
use wabt;
|
||||||
use wasmtime_execute::{Code, InstanceWorld, NullResolver};
|
use wasmtime_execute::{InstancePlus, JITCode, NullResolver};
|
||||||
|
|
||||||
const PATH_MODULE_RS2WASM_ADD_FUNC: &str = r"filetests/rs2wasm-add-func.wat";
|
const PATH_MODULE_RS2WASM_ADD_FUNC: &str = r"filetests/rs2wasm-add-func.wat";
|
||||||
|
|
||||||
@@ -234,8 +191,8 @@ mod tests {
|
|||||||
let isa = isa_builder.finish(settings::Flags::new(flag_builder));
|
let isa = isa_builder.finish(settings::Flags::new(flag_builder));
|
||||||
|
|
||||||
let mut resolver = NullResolver {};
|
let mut resolver = NullResolver {};
|
||||||
let mut code = Code::new();
|
let mut code = JITCode::new();
|
||||||
let world = InstanceWorld::new(&mut code, &*isa, &data, &mut resolver);
|
let instance = InstancePlus::new(&mut code, &*isa, &data, &mut resolver);
|
||||||
assert!(world.is_ok());
|
assert!(instance.is_ok());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -94,7 +94,12 @@ fn main() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let isa = isa_builder.finish(settings::Flags::new(flag_builder));
|
let isa = isa_builder.finish(settings::Flags::new(flag_builder));
|
||||||
let mut wast_context = WastContext::new().expect("Error creating WastContext");
|
let mut wast_context = WastContext::new();
|
||||||
|
|
||||||
|
wast_context
|
||||||
|
.register_spectest()
|
||||||
|
.expect("error instantiating \"spectest\"");
|
||||||
|
|
||||||
for filename in &args.arg_file {
|
for filename in &args.arg_file {
|
||||||
wast_context
|
wast_context
|
||||||
.run_file(&*isa, Path::new(&filename))
|
.run_file(&*isa, Path::new(&filename))
|
||||||
@@ -8,7 +8,7 @@ use cranelift_codegen::settings::Configurable;
|
|||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use wasmtime_wast::WastContext;
|
use wasmtime_wast::WastContext;
|
||||||
|
|
||||||
include!(concat!(env!("OUT_DIR"), "/run_wast_files.rs"));
|
include!(concat!(env!("OUT_DIR"), "/wast_testsuite_tests.rs"));
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
fn native_isa() -> Box<isa::TargetIsa> {
|
fn native_isa() -> Box<isa::TargetIsa> {
|
||||||
@@ -18,5 +18,6 @@ fn native_isa() -> Box<isa::TargetIsa> {
|
|||||||
let isa_builder = cranelift_native::builder().unwrap_or_else(|_| {
|
let isa_builder = cranelift_native::builder().unwrap_or_else(|_| {
|
||||||
panic!("host machine is not a supported target");
|
panic!("host machine is not a supported target");
|
||||||
});
|
});
|
||||||
|
|
||||||
isa_builder.finish(settings::Flags::new(flag_builder))
|
isa_builder.finish(settings::Flags::new(flag_builder))
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user