Support imports.

This commit is contained in:
Dan Gohman
2018-12-06 02:02:44 -05:00
parent 8170a9db79
commit d9b4bd1de8
23 changed files with 1312 additions and 268 deletions

View File

@@ -7,7 +7,9 @@ use cranelift_codegen::ir::ExternalName;
use cranelift_codegen::isa;
use cranelift_codegen::Context;
use cranelift_entity::{EntityRef, PrimaryMap};
use cranelift_wasm::{DefinedFuncIndex, FuncIndex, FuncTranslator};
use cranelift_wasm::{
DefinedFuncIndex, FuncIndex, FuncTranslator, GlobalIndex, MemoryIndex, TableIndex,
};
use environ::{get_func_name, get_memory_grow_name, get_memory_size_name, ModuleTranslation};
use std::string::{String, ToString};
use std::vec::Vec;
@@ -17,12 +19,30 @@ use std::vec::Vec;
pub struct Compilation {
/// Compiled machine code for the function bodies.
pub functions: PrimaryMap<DefinedFuncIndex, Vec<u8>>,
/// Resolved function addresses for imported functions.
pub resolved_func_imports: PrimaryMap<FuncIndex, usize>,
/// Resolved function addresses for imported tables.
pub resolved_table_imports: PrimaryMap<TableIndex, usize>,
/// Resolved function addresses for imported globals.
pub resolved_global_imports: PrimaryMap<GlobalIndex, usize>,
/// Resolved function addresses for imported memories.
pub resolved_memory_imports: PrimaryMap<MemoryIndex, usize>,
}
impl Compilation {
/// Allocates the compilation result with the given function bodies.
pub fn new(functions: PrimaryMap<DefinedFuncIndex, Vec<u8>>) -> Self {
Self { functions }
Self {
functions,
resolved_func_imports: PrimaryMap::new(),
resolved_table_imports: PrimaryMap::new(),
resolved_memory_imports: PrimaryMap::new(),
resolved_global_imports: PrimaryMap::new(),
}
}
}
@@ -145,5 +165,6 @@ pub fn compile_module<'data, 'module>(
functions.push(code_buf);
relocations.push(reloc_sink.func_relocs);
}
// TODO: Reorganize where we create the Vec for the resolved imports.
Ok((Compilation::new(functions), relocations))
}

View File

@@ -2,7 +2,7 @@ use cast;
use cranelift_codegen::cursor::FuncCursor;
use cranelift_codegen::ir;
use cranelift_codegen::ir::condcodes::*;
use cranelift_codegen::ir::immediates::{Imm64, Offset32, Uimm64};
use cranelift_codegen::ir::immediates::{Offset32, Uimm64};
use cranelift_codegen::ir::types::*;
use cranelift_codegen::ir::{
AbiParam, ArgumentPurpose, ExtFuncData, FuncRef, Function, InstBuilder, Signature,
@@ -169,11 +169,7 @@ impl<'data, 'module> cranelift_wasm::ModuleEnvironment<'data>
}
fn declare_signature(&mut self, sig: &ir::Signature) {
let mut sig = sig.clone();
sig.params.push(AbiParam::special(
self.pointer_type(),
ArgumentPurpose::VMContext,
));
let sig = translate_signature(sig.clone(), self.pointer_type());
// TODO: Deduplicate signatures.
self.module.signatures.push(sig);
}
@@ -207,8 +203,17 @@ impl<'data, 'module> cranelift_wasm::ModuleEnvironment<'data>
self.module.functions[func_index]
}
fn declare_global_import(&mut self, _global: Global, _module: &str, _field: &str) {
unimplemented!("imported globals");
fn declare_global_import(&mut self, global: Global, module: &str, field: &str) {
debug_assert_eq!(
self.module.globals.len(),
self.module.imported_globals.len(),
"Imported globals must be declared first"
);
self.module.globals.push(global);
self.module
.imported_globals
.push((String::from(module), String::from(field)));
}
fn declare_global(&mut self, global: Global) {
@@ -219,8 +224,18 @@ impl<'data, 'module> cranelift_wasm::ModuleEnvironment<'data>
&self.module.globals[global_index]
}
fn declare_table_import(&mut self, _table: Table, _module: &str, _field: &str) {
unimplemented!("imported tables");
fn declare_table_import(&mut self, table: Table, module: &str, field: &str) {
debug_assert_eq!(
self.module.table_plans.len(),
self.module.imported_tables.len(),
"Imported tables must be declared first"
);
let plan = TablePlan::for_table(table, &self.tunables);
self.module.table_plans.push(plan);
self.module
.imported_tables
.push((String::from(module), String::from(field)));
}
fn declare_table(&mut self, table: Table) {
@@ -235,7 +250,6 @@ impl<'data, 'module> cranelift_wasm::ModuleEnvironment<'data>
offset: usize,
elements: Vec<FuncIndex>,
) {
debug_assert!(base.is_none(), "global-value offsets not supported yet");
self.module.table_elements.push(TableElements {
table_index,
base,
@@ -244,8 +258,18 @@ impl<'data, 'module> cranelift_wasm::ModuleEnvironment<'data>
});
}
fn declare_memory_import(&mut self, _memory: Memory, _module: &str, _field: &str) {
unimplemented!("imported memories");
fn declare_memory_import(&mut self, memory: Memory, module: &str, field: &str) {
debug_assert_eq!(
self.module.memory_plans.len(),
self.module.imported_memories.len(),
"Imported memories must be declared first"
);
let plan = MemoryPlan::for_memory(memory, &self.tunables);
self.module.memory_plans.push(plan);
self.module
.imported_memories
.push((String::from(module), String::from(field)));
}
fn declare_memory(&mut self, memory: Memory) {
@@ -260,7 +284,6 @@ impl<'data, 'module> cranelift_wasm::ModuleEnvironment<'data>
offset: usize,
data: &'data [u8],
) {
debug_assert!(base.is_none(), "global-value offsets not supported yet");
self.lazy.data_initializers.push(DataInitializer {
memory_index,
base,
@@ -313,7 +336,7 @@ impl<'module_environment> cranelift_wasm::FuncEnvironment for FuncEnvironment<'m
let pointer_type = self.pointer_type();
let vmctx = self.vmctx(func);
let globals_base = self.globals_base.unwrap_or_else(|| {
let mut globals_base = self.globals_base.unwrap_or_else(|| {
let new_base = func.create_global_value(ir::GlobalValueData::Load {
base: vmctx,
offset: Offset32::new(i32::from(self.offsets.vmctx_globals())),
@@ -323,13 +346,24 @@ impl<'module_environment> cranelift_wasm::FuncEnvironment for FuncEnvironment<'m
self.globals_base = Some(new_base);
new_base
});
let gv = func.create_global_value(ir::GlobalValueData::IAddImm {
let mut offset = self.offsets.index_vmglobal(index.as_u32());
// For imported memories, the `VMGlobal` array contains a pointer to
// the `VMGlobalDefinition` rather than containing the `VMGlobalDefinition`
// inline, so we do an extra indirection.
if self.module.is_imported_global(index) {
globals_base = func.create_global_value(ir::GlobalValueData::Load {
base: globals_base,
offset: Imm64::new(i64::from(self.offsets.index_vmglobal(index.as_u32()))),
offset: Offset32::new(self.offsets.index_vmglobal_import_from(index.as_u32())),
global_type: pointer_type,
readonly: true,
});
offset = self.offsets.index_vmglobal(0);
}
GlobalVariable::Memory {
gv,
gv: globals_base,
offset: offset.into(),
ty: self.module.globals[index].ty,
}
}
@@ -338,7 +372,7 @@ impl<'module_environment> cranelift_wasm::FuncEnvironment for FuncEnvironment<'m
let pointer_type = self.pointer_type();
let vmctx = self.vmctx(func);
let memories_base = self.memories_base.unwrap_or_else(|| {
let mut memories_base = self.memories_base.unwrap_or_else(|| {
let new_base = func.create_global_value(ir::GlobalValueData::Load {
base: vmctx,
offset: Offset32::new(i32::from(self.offsets.vmctx_memories())),
@@ -348,6 +382,25 @@ impl<'module_environment> cranelift_wasm::FuncEnvironment for FuncEnvironment<'m
self.memories_base = Some(new_base);
new_base
});
let mut base_offset = self.offsets.index_vmmemory_definition_base(index.as_u32());
let mut current_length_offset = self
.offsets
.index_vmmemory_definition_current_length(index.as_u32());
// For imported memories, the `VMMemory` array contains a pointer to
// the `VMMemoryDefinition` rather than containing the `VMMemoryDefinition`
// inline, so we do an extra indirection.
if self.module.is_imported_memory(index) {
memories_base = func.create_global_value(ir::GlobalValueData::Load {
base: memories_base,
offset: Offset32::new(self.offsets.index_vmmemory_import_from(index.as_u32())),
global_type: pointer_type,
readonly: true,
});
base_offset = self.offsets.index_vmmemory_definition_base(0);
current_length_offset = self.offsets.index_vmmemory_definition_current_length(0);
}
// If we have a declared maximum, we can make this a "static" heap, which is
// allocated up front and never moved.
let (offset_guard_size, heap_style, readonly_base) = match self.module.memory_plans[index] {
@@ -358,9 +411,7 @@ impl<'module_environment> cranelift_wasm::FuncEnvironment for FuncEnvironment<'m
} => {
let heap_bound = func.create_global_value(ir::GlobalValueData::Load {
base: memories_base,
offset: Offset32::new(
self.offsets.index_vmmemory_current_length(index.as_u32()),
),
offset: Offset32::new(current_length_offset),
global_type: I32,
readonly: false,
});
@@ -387,7 +438,7 @@ impl<'module_environment> cranelift_wasm::FuncEnvironment for FuncEnvironment<'m
let heap_base = func.create_global_value(ir::GlobalValueData::Load {
base: memories_base,
offset: Offset32::new(self.offsets.index_vmmemory_base(index.as_u32())),
offset: Offset32::new(base_offset),
global_type: pointer_type,
readonly: readonly_base,
});
@@ -404,7 +455,7 @@ impl<'module_environment> cranelift_wasm::FuncEnvironment for FuncEnvironment<'m
let pointer_type = self.pointer_type();
let vmctx = self.vmctx(func);
let tables_base = self.tables_base.unwrap_or_else(|| {
let mut tables_base = self.tables_base.unwrap_or_else(|| {
let new_base = func.create_global_value(ir::GlobalValueData::Load {
base: vmctx,
offset: Offset32::new(i32::from(self.offsets.vmctx_tables())),
@@ -414,15 +465,34 @@ impl<'module_environment> cranelift_wasm::FuncEnvironment for FuncEnvironment<'m
self.tables_base = Some(new_base);
new_base
});
let mut base_offset = self.offsets.index_vmtable_definition_base(index.as_u32());
let mut current_elements_offset = self
.offsets
.index_vmtable_definition_current_elements(index.as_u32());
// For imported tables, the `VMTable` array contains a pointer to
// the `VMTableDefinition` rather than containing the `VMTableDefinition`
// inline, so we do an extra indirection.
if self.module.is_imported_table(index) {
tables_base = func.create_global_value(ir::GlobalValueData::Load {
base: tables_base,
offset: Offset32::new(self.offsets.index_vmtable_import_from(index.as_u32())),
global_type: pointer_type,
readonly: true,
});
base_offset = self.offsets.index_vmtable_definition_base(0);
current_elements_offset = self.offsets.index_vmtable_definition_current_elements(0);
}
let base_gv = func.create_global_value(ir::GlobalValueData::Load {
base: tables_base,
offset: Offset32::new(self.offsets.index_vmtable_base(index.as_u32())),
offset: Offset32::new(base_offset),
global_type: pointer_type,
readonly: false,
});
let bound_gv = func.create_global_value(ir::GlobalValueData::Load {
base: tables_base,
offset: Offset32::new(self.offsets.index_vmtable_current_elements(index.as_u32())),
offset: Offset32::new(current_elements_offset),
global_type: I32,
readonly: false,
});
@@ -492,7 +562,7 @@ impl<'module_environment> cranelift_wasm::FuncEnvironment for FuncEnvironment<'m
let sig_id_type = Type::int(u16::from(sig_id_size) * 8).unwrap();
let vmctx = self.vmctx(pos.func);
let signature_ids_base = self.globals_base.unwrap_or_else(|| {
let signature_ids_base = self.signature_ids_base.unwrap_or_else(|| {
let new_base = pos.func.create_global_value(ir::GlobalValueData::Load {
base: vmctx,
offset: Offset32::new(i32::from(self.offsets.vmctx_signature_ids())),
@@ -563,13 +633,13 @@ impl<'module_environment> cranelift_wasm::FuncEnvironment for FuncEnvironment<'m
) -> WasmResult<ir::Value> {
let memory_grow_func = self.memory_grow_extfunc.unwrap_or_else(|| {
let sig_ref = pos.func.import_signature(Signature {
call_conv: self.isa.frontend_config().default_call_conv,
params: vec![
AbiParam::new(I32),
AbiParam::new(I32),
AbiParam::special(self.pointer_type(), ArgumentPurpose::VMContext),
],
returns: vec![AbiParam::new(I32)],
call_conv: self.isa.frontend_config().default_call_conv,
});
// We currently allocate all code segments independently, so nothing
// is colocated.
@@ -597,12 +667,12 @@ impl<'module_environment> cranelift_wasm::FuncEnvironment for FuncEnvironment<'m
) -> WasmResult<ir::Value> {
let memory_size_func = self.memory_size_extfunc.unwrap_or_else(|| {
let sig_ref = pos.func.import_signature(Signature {
call_conv: self.isa.frontend_config().default_call_conv,
params: vec![
AbiParam::new(I32),
AbiParam::special(self.pointer_type(), ArgumentPurpose::VMContext),
],
returns: vec![AbiParam::new(I32)],
call_conv: self.isa.frontend_config().default_call_conv,
});
// We currently allocate all code segments independently, so nothing
// is colocated.
@@ -642,3 +712,10 @@ impl<'data, 'module> ModuleTranslation<'data, 'module> {
FuncEnvironment::new(self.isa, &self.module)
}
}
/// Add environment-specific function parameters.
pub fn translate_signature(mut sig: ir::Signature, pointer_type: ir::Type) -> ir::Signature {
sig.params
.push(AbiParam::special(pointer_type, ArgumentPurpose::VMContext));
sig
}

View File

@@ -44,7 +44,7 @@ mod vmoffsets;
pub use compilation::{
compile_module, Compilation, RelocSink, Relocation, RelocationTarget, Relocations,
};
pub use environ::{ModuleEnvironment, ModuleTranslation};
pub use environ::{translate_signature, ModuleEnvironment, ModuleTranslation};
pub use module::{
DataInitializer, Export, MemoryPlan, MemoryStyle, Module, TableElements, TablePlan, TableStyle,
};

View File

@@ -139,6 +139,15 @@ pub struct Module {
/// Names of imported functions.
pub imported_funcs: PrimaryMap<FuncIndex, (String, String)>,
/// Names of imported tables.
pub imported_tables: PrimaryMap<TableIndex, (String, String)>,
/// Names of imported globals.
pub imported_globals: PrimaryMap<GlobalIndex, (String, String)>,
/// Names of imported memories.
pub imported_memories: PrimaryMap<MemoryIndex, (String, String)>,
/// Types of functions, imported and local.
pub functions: PrimaryMap<FuncIndex, SignatureIndex>,
@@ -167,6 +176,9 @@ impl Module {
Self {
signatures: PrimaryMap::new(),
imported_funcs: PrimaryMap::new(),
imported_memories: PrimaryMap::new(),
imported_globals: PrimaryMap::new(),
imported_tables: PrimaryMap::new(),
functions: PrimaryMap::new(),
table_plans: PrimaryMap::new(),
memory_plans: PrimaryMap::new(),
@@ -193,6 +205,26 @@ impl Module {
))
}
}
/// Test whether the given function index is for an imported function.
pub fn is_imported_function(&self, index: FuncIndex) -> bool {
index.index() < self.imported_funcs.len()
}
/// Test whether the given table index is for an imported table.
pub fn is_imported_table(&self, index: TableIndex) -> bool {
index.index() < self.imported_tables.len()
}
/// Test whether the given global index is for an imported global.
pub fn is_imported_global(&self, index: GlobalIndex) -> bool {
index.index() < self.imported_globals.len()
}
/// Test whether the given memory index is for an imported memory.
pub fn is_imported_memory(&self, index: MemoryIndex) -> bool {
index.index() < self.imported_memories.len()
}
}
/// A data initializer for linear memory.

View File

@@ -1,3 +1,6 @@
//! Offsets and sizes of various structs in wasmtime-execute's vmcontext
//! module.
/// This class computes offsets to fields within `VMContext` and other
/// related structs that JIT code accesses directly.
pub struct VMOffsets {
@@ -11,44 +14,108 @@ impl VMOffsets {
}
}
/// Offsets for `wasmtime_execute::VMMemory`.
/// Offsets for `wasmtime_execute::VMMemoryDefinition`.
impl VMOffsets {
/// The offset of the `base` field.
pub fn vmmemory_base(&self) -> u8 {
pub fn vmmemory_definition_base(&self) -> u8 {
0 * self.pointer_size
}
/// The offset of the `current_length` field.
pub fn vmmemory_current_length(&self) -> u8 {
pub fn vmmemory_definition_current_length(&self) -> u8 {
1 * self.pointer_size
}
/// Return the size of `VMMemoryDefinition`.
pub fn size_of_vmmemory_definition(&self) -> u8 {
2 * self.pointer_size
}
}
/// Offsets for `wasmtime_execute::VMMemoryImport`.
impl VMOffsets {
/// The offset of the `from` field.
pub fn vmmemory_import_from(&self) -> u8 {
0 * self.pointer_size
}
/// Return the size of `VMMemoryImport`.
pub fn size_of_vmmemory_import(&self) -> u8 {
1 * self.pointer_size
}
}
/// Offsets for `wasmtime_execute::VMMemory`.
impl VMOffsets {
/// Return the size of `VMMemory`.
pub fn size_of_vmmemory(&self) -> u8 {
2 * self.pointer_size
}
}
/// Offsets for `wasmtime_execute::VMGlobalDefinition`.
impl VMOffsets {
/// Return the size of `VMGlobalDefinition`.
pub fn size_of_vmglobal_definition(&self) -> u8 {
8
}
}
/// Offsets for `wasmtime_execute::VMGlobalImport`.
impl VMOffsets {
/// The offset of the `from` field.
pub fn vmglobal_import_from(&self) -> u8 {
0 * self.pointer_size
}
/// Return the size of `VMGlobalImport`.
pub fn size_of_vmglobal_import(&self) -> u8 {
1 * self.pointer_size
}
}
/// Offsets for `wasmtime_execute::VMGlobal`.
impl VMOffsets {
/// Return the size of `VMGlobal`.
pub fn size_of_vmglobal(&self) -> u8 {
8
assert!(self.size_of_vmglobal_import() <= self.size_of_vmglobal_definition());
self.size_of_vmglobal_definition()
}
}
/// Offsets for `wasmtime_execute::VMTableDefinition`.
impl VMOffsets {
/// The offset of the `base` field.
pub fn vmtable_definition_base(&self) -> u8 {
0 * self.pointer_size
}
/// The offset of the `current_elements` field.
pub fn vmtable_definition_current_elements(&self) -> u8 {
1 * self.pointer_size
}
/// Return the size of `VMTableDefinition`.
pub fn size_of_vmtable_definition(&self) -> u8 {
2 * self.pointer_size
}
}
/// Offsets for `wasmtime_execute::VMTableImport`.
impl VMOffsets {
/// The offset of the `from` field.
pub fn vmtable_import_from(&self) -> u8 {
0 * self.pointer_size
}
/// Return the size of `VMTableImport`.
pub fn size_of_vmtable_import(&self) -> u8 {
1 * self.pointer_size
}
}
/// Offsets for `wasmtime_execute::VMTable`.
impl VMOffsets {
/// The offset of the `base` field.
pub fn vmtable_base(&self) -> u8 {
0 * self.pointer_size
}
/// The offset of the `current_elements` field.
pub fn vmtable_current_elements(&self) -> u8 {
1 * self.pointer_size
}
/// Return the size of `VMTable`.
pub fn size_of_vmtable(&self) -> u8 {
2 * self.pointer_size
@@ -141,33 +208,57 @@ impl VMOffsets {
/// Return the offset from the `memories` pointer to the `base` field in
/// `VMMemory` index `index`.
pub fn index_vmmemory_base(&self, index: u32) -> i32 {
pub fn index_vmmemory_definition_base(&self, index: u32) -> i32 {
self.index_vmmemory(index)
.checked_add(i32::from(self.vmmemory_base()))
.checked_add(i32::from(self.vmmemory_definition_base()))
.unwrap()
}
/// Return the offset from the `memories` pointer to the `current_length` field in
/// `VMMemory` index `index`.
pub fn index_vmmemory_current_length(&self, index: u32) -> i32 {
/// `VMMemoryDefinition` index `index`.
pub fn index_vmmemory_definition_current_length(&self, index: u32) -> i32 {
self.index_vmmemory(index)
.checked_add(i32::from(self.vmmemory_current_length()))
.checked_add(i32::from(self.vmmemory_definition_current_length()))
.unwrap()
}
/// Return the offset from the `memories` pointer to the `from` field in
/// `VMMemoryImport` index `index`.
pub fn index_vmmemory_import_from(&self, index: u32) -> i32 {
self.index_vmmemory(index)
.checked_add(i32::from(self.vmmemory_import_from()))
.unwrap()
}
/// Return the offset from the `globals` pointer to the `from` field in
/// `VMGlobal` index `index`.
pub fn index_vmglobal_import_from(&self, index: u32) -> i32 {
self.index_vmglobal(index)
.checked_add(i32::from(self.vmglobal_import_from()))
.unwrap()
}
/// Return the offset from the `tables` pointer to the `base` field in
/// `VMTable` index `index`.
pub fn index_vmtable_base(&self, index: u32) -> i32 {
pub fn index_vmtable_definition_base(&self, index: u32) -> i32 {
self.index_vmtable(index)
.checked_add(i32::from(self.vmtable_base()))
.checked_add(i32::from(self.vmtable_definition_base()))
.unwrap()
}
/// Return the offset from the `tables` pointer to the `current_elements` field in
/// `VMTable` index `index`.
pub fn index_vmtable_current_elements(&self, index: u32) -> i32 {
pub fn index_vmtable_definition_current_elements(&self, index: u32) -> i32 {
self.index_vmtable(index)
.checked_add(i32::from(self.vmtable_current_elements()))
.checked_add(i32::from(self.vmtable_definition_current_elements()))
.unwrap()
}
/// Return the offset from the `tables` pointer to the `from` field in
/// `VMTableImport` index `index`.
pub fn index_vmtable_import_from(&self, index: u32) -> i32 {
self.index_vmtable(index)
.checked_add(i32::from(self.vmtable_import_from()))
.unwrap()
}
}

View File

@@ -5,66 +5,282 @@ use code::Code;
use cranelift_codegen::binemit::Reloc;
use cranelift_codegen::isa::TargetIsa;
use cranelift_entity::{EntityRef, PrimaryMap};
use cranelift_wasm::{DefinedFuncIndex, MemoryIndex};
use cranelift_wasm::{
DefinedFuncIndex, Global, GlobalInit, Memory, MemoryIndex, Table, TableElementType,
};
use export::{ExportValue, Resolver};
use instance::Instance;
use invoke::{invoke_by_index, InvokeOutcome};
use region::protect;
use region::Protection;
use region::{protect, Protection};
use std::ptr::write_unaligned;
use std::string::String;
use std::vec::Vec;
use vmcontext::VMContext;
use wasmtime_environ::{
compile_module, Compilation, Module, ModuleTranslation, Relocation, RelocationTarget,
compile_module, Compilation, MemoryPlan, MemoryStyle, Module, ModuleTranslation, Relocation,
RelocationTarget, TablePlan, TableStyle,
};
/// Executes a module that has been translated with the `wasmtime-environ` environment
/// implementation.
pub fn compile_and_link_module<'data, 'module, F>(
pub fn compile_and_link_module<'data, 'module>(
isa: &TargetIsa,
translation: &ModuleTranslation<'data, 'module>,
imports: F,
) -> Result<Compilation, String>
where
F: Fn(&str, &str) -> Option<usize>,
{
resolver: &mut Resolver,
) -> Result<Compilation, String> {
let (mut compilation, relocations) = compile_module(&translation, isa)?;
for (index, (ref module, ref field)) in translation.module.imported_funcs.iter() {
match resolver.resolve(module, field) {
Some(export_value) => match export_value {
ExportValue::Function { address, signature } => {
let import_signature =
&translation.module.signatures[translation.module.functions[index]];
if signature != *import_signature {
return Err(format!(
"{}/{}: exported function with signature {} incompatible with function import with signature {}",
module, field,
signature, import_signature,
));
}
compilation.resolved_func_imports.push(address);
}
ExportValue::Table { .. }
| ExportValue::Memory { .. }
| ExportValue::Global { .. } => {
return Err(format!(
"{}/{}: export not compatible with function import",
module, field
));
}
},
None => return Err(format!("{}/{}: no provided import function", module, field)),
}
}
for (index, (ref module, ref field)) in translation.module.imported_globals.iter() {
match resolver.resolve(module, field) {
Some(export_value) => match export_value {
ExportValue::Global { address, global } => {
let imported_global = translation.module.globals[index];
if !is_global_compatible(&global, &imported_global) {
return Err(format!(
"{}/{}: exported global incompatible with global import",
module, field,
));
}
compilation.resolved_global_imports.push(address as usize);
}
ExportValue::Table { .. }
| ExportValue::Memory { .. }
| ExportValue::Function { .. } => {
return Err(format!(
"{}/{}: exported global incompatible with global import",
module, field
));
}
},
None => {
return Err(format!(
"no provided import global for {}/{}",
module, field
))
}
}
}
for (index, (ref module, ref field)) in translation.module.imported_tables.iter() {
match resolver.resolve(module, field) {
Some(export_value) => match export_value {
ExportValue::Table { address, table } => {
let import_table = &translation.module.table_plans[index];
if !is_table_compatible(&table, import_table) {
return Err(format!(
"{}/{}: exported table incompatible with table import",
module, field,
));
}
compilation.resolved_table_imports.push(address as usize);
}
ExportValue::Global { .. }
| ExportValue::Memory { .. }
| ExportValue::Function { .. } => {
return Err(format!(
"{}/{}: export not compatible with table import",
module, field
));
}
},
None => return Err(format!("no provided import table for {}/{}", module, field)),
}
}
for (index, (ref module, ref field)) in translation.module.imported_memories.iter() {
match resolver.resolve(module, field) {
Some(export_value) => match export_value {
ExportValue::Memory { address, memory } => {
let import_memory = &translation.module.memory_plans[index];
if is_memory_compatible(&memory, import_memory) {
return Err(format!(
"{}/{}: exported memory incompatible with memory import",
module, field
));
}
compilation.resolved_memory_imports.push(address as usize);
}
ExportValue::Table { .. }
| ExportValue::Global { .. }
| ExportValue::Function { .. } => {
return Err(format!(
"{}/{}: export not compatible with memory import",
module, field
));
}
},
None => {
return Err(format!(
"no provided import memory for {}/{}",
module, field
))
}
}
}
// Apply relocations, now that we have virtual addresses for everything.
relocate(&mut compilation, &relocations, &translation.module, imports);
relocate(&mut compilation, &relocations, &translation.module)?;
Ok(compilation)
}
fn is_global_compatible(exported: &Global, imported: &Global) -> bool {
match imported.initializer {
GlobalInit::Import => (),
_ => panic!("imported Global should have an Imported initializer"),
}
let Global {
ty: exported_ty,
mutability: exported_mutability,
initializer: _exported_initializer,
} = exported;
let Global {
ty: imported_ty,
mutability: imported_mutability,
initializer: _imported_initializer,
} = imported;
exported_ty == imported_ty && imported_mutability == exported_mutability
}
fn is_table_style_compatible(exported_style: &TableStyle, imported_style: &TableStyle) -> bool {
match exported_style {
TableStyle::CallerChecksSignature => match imported_style {
TableStyle::CallerChecksSignature => true,
},
}
}
fn is_table_element_type_compatible(
exported_type: TableElementType,
imported_type: TableElementType,
) -> bool {
match exported_type {
TableElementType::Func => match imported_type {
TableElementType::Func => true,
_ => false,
},
TableElementType::Val(exported_val_ty) => match imported_type {
TableElementType::Val(imported_val_ty) => exported_val_ty == imported_val_ty,
_ => false,
},
}
}
fn is_table_compatible(exported: &TablePlan, imported: &TablePlan) -> bool {
let TablePlan {
table:
Table {
ty: exported_ty,
minimum: exported_minimum,
maximum: exported_maximum,
},
style: exported_style,
} = exported;
let TablePlan {
table:
Table {
ty: imported_ty,
minimum: imported_minimum,
maximum: imported_maximum,
},
style: imported_style,
} = imported;
is_table_element_type_compatible(*exported_ty, *imported_ty)
&& imported_minimum >= exported_minimum
&& imported_maximum <= exported_maximum
&& is_table_style_compatible(imported_style, exported_style)
}
fn is_memory_style_compatible(exported_style: &MemoryStyle, imported_style: &MemoryStyle) -> bool {
match exported_style {
MemoryStyle::Dynamic => match imported_style {
MemoryStyle::Dynamic => true,
_ => false,
},
MemoryStyle::Static {
bound: imported_bound,
} => match imported_style {
MemoryStyle::Static {
bound: exported_bound,
} => exported_bound >= imported_bound,
_ => false,
},
}
}
fn is_memory_compatible(exported: &MemoryPlan, imported: &MemoryPlan) -> bool {
let MemoryPlan {
memory:
Memory {
minimum: exported_minimum,
maximum: exported_maximum,
shared: exported_shared,
},
style: exported_style,
offset_guard_size: exported_offset_guard_size,
} = exported;
let MemoryPlan {
memory:
Memory {
minimum: imported_minimum,
maximum: imported_maximum,
shared: imported_shared,
},
style: imported_style,
offset_guard_size: imported_offset_guard_size,
} = imported;
imported_minimum >= exported_minimum
&& imported_maximum <= exported_maximum
&& exported_shared == imported_shared
&& is_memory_style_compatible(exported_style, imported_style)
&& exported_offset_guard_size >= imported_offset_guard_size
}
extern "C" {
pub fn __rust_probestack();
}
/// Performs the relocations inside the function bytecode, provided the necessary metadata
fn relocate<F>(
/// Performs the relocations inside the function bytecode, provided the necessary metadata.
fn relocate(
compilation: &mut Compilation,
relocations: &PrimaryMap<DefinedFuncIndex, Vec<Relocation>>,
module: &Module,
imports: F,
) where
F: Fn(&str, &str) -> Option<usize>,
{
// The relocations are relative to the relocation's address plus four bytes
// TODO: Support architectures other than x64, and other reloc kinds.
) -> Result<(), String> {
// The relocations are relative to the relocation's address plus four bytes.
for (i, function_relocs) in relocations.iter() {
for r in function_relocs {
let target_func_address: usize = match r.reloc_target {
RelocationTarget::UserFunc(index) => match module.defined_func_index(index) {
Some(f) => compilation.functions[f].as_ptr() as usize,
None => {
let func = &module.imported_funcs[index];
match imports(&func.0, &func.1) {
Some(ptr) => ptr,
None => {
panic!("no provided import function for {}/{}", &func.0, &func.1)
}
}
}
None => compilation.resolved_func_imports[index],
},
RelocationTarget::MemoryGrow => wasmtime_memory_grow as usize,
RelocationTarget::MemorySize => wasmtime_memory_size as usize,
@@ -111,6 +327,7 @@ fn relocate<F>(
}
}
}
Ok(())
}
extern "C" fn wasmtime_memory_grow(size: u32, memory_index: u32, vmctx: *mut VMContext) -> u32 {

76
lib/execute/src/export.rs Normal file
View File

@@ -0,0 +1,76 @@
use cranelift_codegen::ir;
use cranelift_wasm::Global;
use vmcontext::{VMGlobal, VMMemory, VMTable};
use wasmtime_environ::{MemoryPlan, TablePlan};
/// The value of an export passed from one instance to another.
pub enum ExportValue {
/// A function export value.
Function {
/// The address of the native-code function.
address: usize,
/// The function signature declaration, used for compatibilty checking.
signature: ir::Signature,
},
/// A table export value.
Table {
/// The address of the table descriptor.
address: *mut VMTable,
/// The table declaration, used for compatibilty checking.
table: TablePlan,
},
/// A memory export value.
Memory {
/// The address of the memory descriptor.
address: *mut VMMemory,
/// The memory declaration, used for compatibilty checking.
memory: MemoryPlan,
},
/// A global export value.
Global {
/// The address of the global storage.
address: *mut VMGlobal,
/// The global declaration, used for compatibilty checking.
global: Global,
},
}
impl ExportValue {
/// Construct a function export value.
pub fn function(address: usize, signature: ir::Signature) -> Self {
ExportValue::Function { address, signature }
}
/// Construct a table export value.
pub fn table(address: *mut VMTable, table: TablePlan) -> Self {
ExportValue::Table { address, table }
}
/// Construct a memory export value.
pub fn memory(address: *mut VMMemory, memory: MemoryPlan) -> Self {
ExportValue::Memory { address, memory }
}
/// Construct a global export value.
pub fn global(address: *mut VMGlobal, global: Global) -> Self {
ExportValue::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<ExportValue>;
}
/// `Resolver` implementation that always resolves to `None`.
pub struct NullResolver {}
impl Resolver for NullResolver {
fn resolve(&mut self, _module: &str, _field: &str) -> Option<ExportValue> {
None
}
}

39
lib/execute/src/get.rs Normal file
View File

@@ -0,0 +1,39 @@
//! Support for reading the value of a wasm global from outside the module.
use cranelift_codegen::ir;
use cranelift_wasm::GlobalIndex;
use invoke::Value;
use std::string::String;
use vmcontext::VMContext;
use wasmtime_environ::{Export, Module};
/// Jumps to the code region of memory and invoke the exported function
pub fn get(module: &Module, vmctx: *mut VMContext, global_name: &str) -> Result<Value, String> {
let global_index = match module.exports.get(global_name) {
Some(Export::Global(index)) => *index,
Some(_) => return Err(format!("exported item \"{}\" is not a global", global_name)),
None => return Err(format!("no export named \"{}\"", global_name)),
};
get_by_index(module, vmctx, global_index)
}
pub fn get_by_index(
module: &Module,
vmctx: *mut VMContext,
global_index: GlobalIndex,
) -> Result<Value, String> {
// TODO: Return Err if the index is out of bounds.
unsafe {
let vmctx = &mut *vmctx;
let vmglobal = vmctx.global(global_index);
let definition = vmglobal.get_definition(module.is_imported_global(global_index));
Ok(match module.globals[global_index].ty {
ir::types::I32 => Value::I32(*definition.as_i32()),
ir::types::I64 => Value::I64(*definition.as_i64()),
ir::types::F32 => Value::F32(*definition.as_f32_bits()),
ir::types::F64 => Value::F64(*definition.as_f64_bits()),
other => return Err(format!("global with type {} not supported", other)),
})
}
}

View File

@@ -6,6 +6,7 @@ use cranelift_entity::PrimaryMap;
use cranelift_wasm::{GlobalIndex, MemoryIndex, TableIndex};
use memory::LinearMemory;
use sig_registry::SignatureRegistry;
use std::ptr;
use std::string::String;
use table::Table;
use vmcontext::{VMCallerCheckedAnyfunc, VMContext, VMGlobal, VMMemory, VMTable};
@@ -44,7 +45,7 @@ impl Instance {
compilation: &Compilation,
data_initializers: &[DataInitializer],
) -> Result<Self, String> {
let mut sig_registry = SignatureRegistry::new();
let mut sig_registry = instantiate_signatures(module);
let mut memories = instantiate_memories(module, data_initializers)?;
let mut tables = instantiate_tables(module, compilation, &mut sig_registry);
@@ -131,6 +132,14 @@ impl Instance {
}
}
fn instantiate_signatures(module: &Module) -> SignatureRegistry {
let mut sig_registry = SignatureRegistry::new();
for (sig_index, sig) in module.signatures.iter() {
sig_registry.register(sig_index, sig);
}
sig_registry
}
/// Allocate memory for just the memories of the current module.
fn instantiate_memories(
module: &Module,
@@ -171,7 +180,7 @@ fn instantiate_tables(
let code_buf = &compilation.functions[module
.defined_func_index(*func_idx)
.expect("table element initializer with imported function not supported yet")];
let type_id = sig_registry.register(callee_sig, &module.signatures[callee_sig]);
let type_id = sig_registry.lookup(callee_sig);
subslice[i] = VMCallerCheckedAnyfunc {
func_ptr: code_buf.as_ptr(),
type_id,
@@ -187,8 +196,13 @@ fn instantiate_tables(
fn instantiate_globals(module: &Module) -> PrimaryMap<GlobalIndex, VMGlobal> {
let mut vmctx_globals = PrimaryMap::with_capacity(module.globals.len());
for _ in 0..module.globals.len() {
vmctx_globals.push(VMGlobal::default());
for (index, global) in module.globals.iter() {
if module.is_imported_global(index) {
// FIXME: get the actual import
vmctx_globals.push(VMGlobal::import(ptr::null_mut()));
} else {
vmctx_globals.push(VMGlobal::definition(global));
}
}
vmctx_globals

View File

@@ -114,12 +114,16 @@ pub fn invoke_by_index(
fn_index: FuncIndex,
args: &[Value],
) -> Result<InvokeOutcome, String> {
let code_buf = &compilation.functions[module
.defined_func_index(fn_index)
.expect("imported start functions not supported yet")];
let sig = &module.signatures[module.functions[fn_index]];
// TODO: Return Err if fn_index is out of bounds.
let exec_code_buf = match module.defined_func_index(fn_index) {
Some(def_fn_index) => {
let code_buf = &compilation.functions[def_fn_index];
code.allocate_copy_of_slice(&code_buf)?.as_ptr() as usize
}
None => compilation.resolved_func_imports[fn_index],
};
let exec_code_buf = code.allocate_copy_of_slice(&code_buf)?.as_ptr();
let sig = &module.signatures[module.functions[fn_index]];
// TODO: Move this out to be done once per thread rather than per call.
let mut traps = TrapContext {
@@ -138,7 +142,7 @@ pub fn invoke_by_index(
return Err("failed to install signal handlers".to_string());
}
call_through_wrapper(code, isa, exec_code_buf as usize, vmctx, args, &sig)
call_through_wrapper(code, isa, exec_code_buf, vmctx, args, &sig)
}
fn call_through_wrapper(

View File

@@ -43,6 +43,8 @@ extern crate cast;
mod code;
mod execute;
mod export;
mod get;
mod instance;
mod invoke;
mod libcalls;
@@ -57,10 +59,12 @@ mod world;
pub use code::Code;
pub use execute::{compile_and_link_module, finish_instantiation};
pub use export::{ExportValue, NullResolver, Resolver};
pub use get::get;
pub use instance::Instance;
pub use invoke::{invoke, InvokeOutcome, Value};
pub use traphandlers::{call_wasm, LookupCodeSegment, RecordTrap, Unwind};
pub use vmcontext::VMContext;
pub use vmcontext::{VMContext, VMGlobal, VMMemory, VMTable};
pub use world::InstanceWorld;
#[cfg(not(feature = "std"))]

View File

@@ -124,8 +124,9 @@ impl LinearMemory {
Some(prev_pages)
}
/// Return a `VMMemory` for exposing the memory to JIT code.
pub fn vmmemory(&mut self) -> VMMemory {
VMMemory::new(self.mmap.as_mut_ptr(), self.mmap.len())
VMMemory::definition(self.mmap.as_mut_ptr(), self.mmap.len())
}
}

View File

@@ -3,7 +3,7 @@
use cast;
use cranelift_codegen::ir;
use cranelift_entity::SecondaryMap;
use cranelift_entity::PrimaryMap;
use cranelift_wasm::SignatureIndex;
use std::collections::{hash_map, HashMap};
use vmcontext::VMSignatureId;
@@ -11,14 +11,14 @@ use vmcontext::VMSignatureId;
#[derive(Debug)]
pub struct SignatureRegistry {
signature_hash: HashMap<ir::Signature, VMSignatureId>,
signature_ids: SecondaryMap<SignatureIndex, VMSignatureId>,
signature_ids: PrimaryMap<SignatureIndex, VMSignatureId>,
}
impl SignatureRegistry {
pub fn new() -> Self {
Self {
signature_hash: HashMap::new(),
signature_ids: SecondaryMap::new(),
signature_ids: PrimaryMap::new(),
}
}
@@ -27,7 +27,12 @@ impl SignatureRegistry {
}
/// Register the given signature.
pub fn register(&mut self, sig_index: SignatureIndex, sig: &ir::Signature) -> VMSignatureId {
pub fn register(&mut self, sig_index: SignatureIndex, sig: &ir::Signature) {
// TODO: Refactor this interface so that we're not passing in redundant
// information.
debug_assert_eq!(sig_index.index(), self.signature_ids.len());
use cranelift_entity::EntityRef;
let len = self.signature_hash.len();
let sig_id = match self.signature_hash.entry(sig.clone()) {
hash_map::Entry::Occupied(entry) => *entry.get(),
@@ -37,7 +42,11 @@ impl SignatureRegistry {
sig_id
}
};
self.signature_ids[sig_index] = sig_id;
sig_id
self.signature_ids.push(sig_id);
}
/// Return the identifying runtime index for the given signature.
pub fn lookup(&mut self, sig_index: SignatureIndex) -> VMSignatureId {
self.signature_ids[sig_index]
}
}

View File

@@ -39,8 +39,9 @@ impl Table {
}
}
/// Return a `VMTable` for exposing the table to JIT code.
pub fn vmtable(&mut self) -> VMTable {
VMTable::new(self.vec.as_mut_ptr() as *mut u8, self.vec.len())
VMTable::definition(self.vec.as_mut_ptr() as *mut u8, self.vec.len())
}
}

View File

@@ -2,20 +2,86 @@
//! fields that JIT code accesses directly.
use cranelift_entity::EntityRef;
use cranelift_wasm::{GlobalIndex, MemoryIndex, TableIndex};
use cranelift_wasm::{Global, GlobalIndex, GlobalInit, MemoryIndex, TableIndex};
use instance::Instance;
use std::mem::size_of;
use std::fmt;
use std::ptr;
use std::slice;
/// The main fields a JIT needs to access to utilize a WebAssembly linear,
/// memory, namely the start address and the size in bytes.
#[derive(Debug)]
/// The fields a JIT needs to access to utilize a WebAssembly linear
/// memory defined within the instance, namely the start address and the
/// size in bytes.
#[derive(Debug, Copy, Clone)]
#[repr(C)]
pub struct VMMemory {
pub struct VMMemoryDefinition {
/// The start address.
base: *mut u8,
/// The current size of linear memory in bytes.
current_length: usize,
// If more elements are added here, remember to add offset_of tests below!
}
#[cfg(test)]
mod test_vmmemory_definition {
use super::VMMemoryDefinition;
use std::mem::size_of;
use wasmtime_environ::VMOffsets;
#[test]
fn check_vmmemory_definition_offsets() {
let offsets = VMOffsets::new(size_of::<*mut u8>() as u8);
assert_eq!(
size_of::<VMMemoryDefinition>(),
usize::from(offsets.size_of_vmmemory_definition())
);
assert_eq!(
offset_of!(VMMemoryDefinition, base),
usize::from(offsets.vmmemory_definition_base())
);
assert_eq!(
offset_of!(VMMemoryDefinition, current_length),
usize::from(offsets.vmmemory_definition_current_length())
);
}
}
/// The fields a JIT needs to access to utilize a WebAssembly linear
/// memory imported from another instance.
#[derive(Debug, Copy, Clone)]
#[repr(C)]
pub struct VMMemoryImport {
/// A pointer to the imported memory description.
from: *mut VMMemoryDefinition,
}
#[cfg(test)]
mod test_vmmemory_import {
use super::VMMemoryImport;
use std::mem::size_of;
use wasmtime_environ::VMOffsets;
#[test]
fn check_vmmemory_import_offsets() {
let offsets = VMOffsets::new(size_of::<*mut u8>() as u8);
assert_eq!(
size_of::<VMMemoryImport>(),
usize::from(offsets.size_of_vmmemory_import())
);
assert_eq!(
offset_of!(VMMemoryImport, from),
usize::from(offsets.vmmemory_import_from())
);
}
}
/// The main fields a JIT needs to access to utilize a WebAssembly linear
/// memory. It must know whether the memory is defined within the instance
/// or imported.
#[repr(C)]
pub union VMMemory {
/// A linear memory defined within the instance.
definition: VMMemoryDefinition,
/// An imported linear memory.
import: VMMemoryImport,
}
#[cfg(test)]
@@ -31,71 +97,155 @@ mod test_vmmemory {
size_of::<VMMemory>(),
usize::from(offsets.size_of_vmmemory())
);
assert_eq!(
offset_of!(VMMemory, base),
usize::from(offsets.vmmemory_base())
);
assert_eq!(
offset_of!(VMMemory, current_length),
usize::from(offsets.vmmemory_current_length())
);
}
}
impl VMMemory {
pub fn new(base: *mut u8, current_length: usize) -> Self {
/// Construct a `VMMemoryDefinition` variant of `VMMemory`.
pub fn definition(base: *mut u8, current_length: usize) -> Self {
Self {
definition: VMMemoryDefinition {
base,
current_length,
},
}
}
pub fn as_slice(&self) -> &[u8] {
unsafe { slice::from_raw_parts(self.base, self.current_length) }
/// Construct a `VMMemoryImmport` variant of `VMMemory`.
pub fn import(from: *mut VMMemoryDefinition) -> Self {
Self {
import: VMMemoryImport { from },
}
}
pub fn as_mut_slice(&mut self) -> &mut [u8] {
unsafe { slice::from_raw_parts_mut(self.base, self.current_length) }
/// Get the underlying `VMMemoryDefinition`.
pub unsafe fn get_definition(&mut self, is_import: bool) -> &mut VMMemoryDefinition {
if is_import {
&mut *self.import.from
} else {
&mut self.definition
}
pub fn as_ptr(&self) -> *const u8 {
self.base
}
pub fn as_mut_ptr(&mut self) -> *mut u8 {
self.base
}
pub fn len(&self) -> usize {
self.current_length
}
}
/// The storage for a WebAssembly global.
impl fmt::Debug for VMMemory {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "VMMemory {{")?;
write!(f, " definition: {:?},", unsafe { self.definition })?;
write!(f, " import: {:?},", unsafe { self.import })?;
write!(f, "}}")?;
Ok(())
}
}
/// The storage for a WebAssembly global defined within the instance.
///
/// TODO: Pack the globals more densely, rather than using the same size
/// for every type.
#[derive(Debug, Clone)]
#[derive(Debug, Copy, Clone)]
#[repr(C, align(8))]
pub struct VMGlobal {
pub struct VMGlobalDefinition {
storage: [u8; 8],
// If more elements are added here, remember to add offset_of tests below!
}
#[cfg(test)]
mod test_vmglobal {
use super::VMGlobal;
mod test_vmglobal_definition {
use super::VMGlobalDefinition;
use std::mem::{align_of, size_of};
use wasmtime_environ::VMOffsets;
#[test]
fn check_vmglobal_alignment() {
assert!(align_of::<VMGlobal>() >= align_of::<i32>());
assert!(align_of::<VMGlobal>() >= align_of::<i64>());
assert!(align_of::<VMGlobal>() >= align_of::<f32>());
assert!(align_of::<VMGlobal>() >= align_of::<f64>());
fn check_vmglobal_definition_alignment() {
assert!(align_of::<VMGlobalDefinition>() >= align_of::<i32>());
assert!(align_of::<VMGlobalDefinition>() >= align_of::<i64>());
assert!(align_of::<VMGlobalDefinition>() >= align_of::<f32>());
assert!(align_of::<VMGlobalDefinition>() >= align_of::<f64>());
}
#[test]
fn check_vmglobal_definition_offsets() {
let offsets = VMOffsets::new(size_of::<*mut u8>() as u8);
assert_eq!(
size_of::<VMGlobalDefinition>(),
usize::from(offsets.size_of_vmglobal_definition())
);
}
}
impl VMGlobalDefinition {
pub unsafe fn as_i32(&mut self) -> &mut i32 {
&mut *(self.storage.as_mut().as_mut_ptr() as *mut u8 as *mut i32)
}
pub unsafe fn as_i64(&mut self) -> &mut i64 {
&mut *(self.storage.as_mut().as_mut_ptr() as *mut u8 as *mut i64)
}
pub unsafe fn as_f32(&mut self) -> &mut f32 {
&mut *(self.storage.as_mut().as_mut_ptr() as *mut u8 as *mut f32)
}
pub unsafe fn as_f32_bits(&mut self) -> &mut u32 {
&mut *(self.storage.as_mut().as_mut_ptr() as *mut u8 as *mut u32)
}
pub unsafe fn as_f64(&mut self) -> &mut f64 {
&mut *(self.storage.as_mut().as_mut_ptr() as *mut u8 as *mut f64)
}
pub unsafe fn as_f64_bits(&mut self) -> &mut u64 {
&mut *(self.storage.as_mut().as_mut_ptr() as *mut u8 as *mut u64)
}
}
/// The fields a JIT needs to access to utilize a WebAssembly global
/// variable imported from another instance.
#[derive(Debug, Copy, Clone)]
#[repr(C)]
pub struct VMGlobalImport {
/// A pointer to the imported global variable description.
from: *mut VMGlobalDefinition,
}
#[cfg(test)]
mod test_vmglobal_import {
use super::VMGlobalImport;
use std::mem::size_of;
use wasmtime_environ::VMOffsets;
#[test]
fn check_vmglobal_import_offsets() {
let offsets = VMOffsets::new(size_of::<*mut u8>() as u8);
assert_eq!(
size_of::<VMGlobalImport>(),
usize::from(offsets.size_of_vmglobal_import())
);
assert_eq!(
offset_of!(VMGlobalImport, from),
usize::from(offsets.vmglobal_import_from())
);
}
}
/// The main fields a JIT needs to access to utilize a WebAssembly global
/// variable. It must know whether the global variable is defined within the
/// instance or imported.
#[repr(C)]
pub union VMGlobal {
/// A global variable defined within the instance.
definition: VMGlobalDefinition,
/// An imported global variable.
import: VMGlobalImport,
}
#[cfg(test)]
mod test_vmglobal {
use super::VMGlobal;
use std::mem::size_of;
use wasmtime_environ::VMOffsets;
#[test]
fn check_vmglobal_offsets() {
let offsets = VMOffsets::new(size_of::<*mut u8>() as u8);
@@ -106,20 +256,122 @@ mod test_vmglobal {
}
}
impl Default for VMGlobal {
fn default() -> Self {
VMGlobal { storage: [0; 8] }
impl VMGlobal {
/// Construct a `VMGlobalDefinition` variant of `VMGlobal`.
pub fn definition(global: &Global) -> Self {
let mut result = VMGlobalDefinition { storage: [0; 8] };
unsafe {
match global.initializer {
GlobalInit::I32Const(x) => *result.as_i32() = x,
GlobalInit::I64Const(x) => *result.as_i64() = x,
GlobalInit::F32Const(x) => *result.as_f32_bits() = x,
GlobalInit::F64Const(x) => *result.as_f64_bits() = x,
GlobalInit::GetGlobal(_x) => unimplemented!("globals init with get_global"),
GlobalInit::Import => panic!("attempting to initialize imported global"),
}
}
Self { definition: result }
}
/// Construct a `VMGlobalImmport` variant of `VMGlobal`.
pub fn import(from: *mut VMGlobalDefinition) -> Self {
Self {
import: VMGlobalImport { from },
}
}
/// Get the underlying `VMGlobalDefinition`.
pub unsafe fn get_definition(&mut self, is_import: bool) -> &mut VMGlobalDefinition {
if is_import {
&mut *self.import.from
} else {
&mut self.definition
}
}
}
#[derive(Debug)]
/// The main fields a JIT needs to access to utilize a WebAssembly table,
/// namely the start address and the number of elements.
impl fmt::Debug for VMGlobal {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "VMGlobal {{")?;
write!(f, " definition: {:?},", unsafe { self.definition })?;
write!(f, " import: {:?},", unsafe { self.import })?;
write!(f, "}}")?;
Ok(())
}
}
/// The fields a JIT needs to access to utilize a WebAssembly table
/// defined within the instance.
#[derive(Debug, Copy, Clone)]
#[repr(C)]
pub struct VMTable {
pub struct VMTableDefinition {
base: *mut u8,
current_elements: usize,
// If more elements are added here, remember to add offset_of tests below!
}
#[cfg(test)]
mod test_vmtable_definition {
use super::VMTableDefinition;
use std::mem::size_of;
use wasmtime_environ::VMOffsets;
#[test]
fn check_vmtable_definition_offsets() {
let offsets = VMOffsets::new(size_of::<*mut u8>() as u8);
assert_eq!(
size_of::<VMTableDefinition>(),
usize::from(offsets.size_of_vmtable_definition())
);
assert_eq!(
offset_of!(VMTableDefinition, base),
usize::from(offsets.vmtable_definition_base())
);
assert_eq!(
offset_of!(VMTableDefinition, current_elements),
usize::from(offsets.vmtable_definition_current_elements())
);
}
}
/// The fields a JIT needs to access to utilize a WebAssembly table
/// imported from another instance.
#[derive(Debug, Copy, Clone)]
#[repr(C)]
pub struct VMTableImport {
/// A pointer to the imported table description.
from: *mut VMTableDefinition,
}
#[cfg(test)]
mod test_vmtable_import {
use super::VMTableImport;
use std::mem::size_of;
use wasmtime_environ::VMOffsets;
#[test]
fn check_vmtable_import_offsets() {
let offsets = VMOffsets::new(size_of::<*mut u8>() as u8);
assert_eq!(
size_of::<VMTableImport>(),
usize::from(offsets.size_of_vmtable_import())
);
assert_eq!(
offset_of!(VMTableImport, from),
usize::from(offsets.vmtable_import_from())
);
}
}
/// The main fields a JIT needs to access to utilize a WebAssembly table.
/// It must know whether the table is defined within the instance
/// or imported.
#[repr(C)]
pub union VMTable {
/// A table defined within the instance.
definition: VMTableDefinition,
/// An imported table.
import: VMTableImport,
}
#[cfg(test)]
@@ -132,43 +384,44 @@ mod test_vmtable {
fn check_vmtable_offsets() {
let offsets = VMOffsets::new(size_of::<*mut u8>() as u8);
assert_eq!(size_of::<VMTable>(), usize::from(offsets.size_of_vmtable()));
assert_eq!(
offset_of!(VMTable, base),
usize::from(offsets.vmtable_base())
);
assert_eq!(
offset_of!(VMTable, current_elements),
usize::from(offsets.vmtable_current_elements())
);
}
}
impl VMTable {
pub fn new(base: *mut u8, current_elements: usize) -> Self {
/// Construct a `VMTableDefinition` variant of `VMTable`.
pub fn definition(base: *mut u8, current_elements: usize) -> Self {
Self {
definition: VMTableDefinition {
base,
current_elements,
},
}
}
pub fn as_slice(&self) -> &[u8] {
unsafe { slice::from_raw_parts(self.base, self.current_elements) }
/// Construct a `VMTableImmport` variant of `VMTable`.
pub fn import(from: *mut VMTableDefinition) -> Self {
Self {
import: VMTableImport { from },
}
}
pub fn as_mut_slice(&mut self) -> &mut [u8] {
unsafe { slice::from_raw_parts_mut(self.base, self.current_elements) }
/// Get the underlying `VMTableDefinition`.
pub unsafe fn get_definition(&mut self, is_import: bool) -> &mut VMTableDefinition {
if is_import {
&mut *self.import.from
} else {
&mut self.definition
}
pub fn as_ptr(&self) -> *const u8 {
self.base
}
}
pub fn as_mut_ptr(&mut self) -> *mut u8 {
self.base
}
pub fn len(&self) -> usize {
self.current_elements
impl fmt::Debug for VMTable {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "VMTable {{")?;
write!(f, " definition: {:?},", unsafe { self.definition })?;
write!(f, " import: {:?},", unsafe { self.import })?;
write!(f, "}}")?;
Ok(())
}
}
@@ -238,6 +491,10 @@ impl Default for VMCallerCheckedAnyfunc {
/// The VM "context", which is pointed to by the `vmctx` arg in Cranelift.
/// This has pointers to the globals, memories, tables, and other runtime
/// state associated with the current instance.
///
/// TODO: The number of memories, globals, tables, and signature IDs does
/// not change dynamically, and pointer arrays are not indexed dynamically,
/// so these fields could all be contiguously allocated.
#[derive(Debug)]
#[repr(C)]
pub struct VMContext {
@@ -300,28 +557,8 @@ impl VMContext {
}
/// Return the base pointer of the globals array.
pub unsafe fn global_storage(&mut self, index: GlobalIndex) -> *mut VMGlobal {
self.globals.add(index.index() * size_of::<VMGlobal>())
}
/// Return a mutable reference to global `index` which has type i32.
pub unsafe fn global_i32(&mut self, index: GlobalIndex) -> &mut i32 {
&mut *(self.global_storage(index) as *mut i32)
}
/// Return a mutable reference to global `index` which has type i64.
pub unsafe fn global_i64(&mut self, index: GlobalIndex) -> &mut i64 {
&mut *(self.global_storage(index) as *mut i64)
}
/// Return a mutable reference to global `index` which has type f32.
pub unsafe fn global_f32(&mut self, index: GlobalIndex) -> &mut f32 {
&mut *(self.global_storage(index) as *mut f32)
}
/// Return a mutable reference to global `index` which has type f64.
pub unsafe fn global_f64(&mut self, index: GlobalIndex) -> &mut f64 {
&mut *(self.global_storage(index) as *mut f64)
pub unsafe fn global(&mut self, index: GlobalIndex) -> &mut VMGlobal {
&mut *self.globals.add(index.index())
}
/// Return a mutable reference to linear memory `index`.

View File

@@ -1,7 +1,13 @@
use cranelift_codegen::isa;
use cranelift_wasm::{GlobalIndex, MemoryIndex};
use export::Resolver;
use std::str;
use vmcontext::VMGlobal;
use wasmtime_environ::{Compilation, Module, ModuleEnvironment, Tunables};
use {compile_and_link_module, finish_instantiation, invoke, Code, Instance, InvokeOutcome, Value};
use {
compile_and_link_module, finish_instantiation, get, invoke, Code, Instance, InvokeOutcome,
Value,
};
/// A module, an instance of that module, and accompanying compilation artifacts.
///
@@ -14,8 +20,14 @@ pub struct InstanceWorld {
impl InstanceWorld {
/// Create a new `InstanceWorld` by compiling the wasm module in `data` and instatiating it.
pub fn new(code: &mut Code, isa: &isa::TargetIsa, data: &[u8]) -> Result<Self, String> {
pub fn new(
code: &mut Code,
isa: &isa::TargetIsa,
data: &[u8],
resolver: &mut Resolver,
) -> Result<Self, String> {
let mut module = Module::new();
// TODO: Allow the tunables to be overridden.
let tunables = Tunables::default();
let (instance, compilation) = {
let translation = {
@@ -24,9 +36,7 @@ impl InstanceWorld {
environ.translate(&data).map_err(|e| e.to_string())?
};
let imports_resolver = |_env: &str, _function: &str| None;
let compilation = compile_and_link_module(isa, &translation, &imports_resolver)?;
let compilation = compile_and_link_module(isa, &translation, resolver)?;
let mut instance = Instance::new(
translation.module,
&compilation,
@@ -64,4 +74,19 @@ impl InstanceWorld {
)
.map_err(|e| e.to_string())
}
/// Read a global in this `InstanceWorld` by name.
pub fn get(&mut self, global_name: &str) -> Result<Value, String> {
get(&self.module, self.instance.vmctx(), global_name).map_err(|e| e.to_string())
}
/// Returns a slice of the contents of allocated linear memory.
pub fn inspect_memory(&self, memory_index: MemoryIndex, address: usize, len: usize) -> &[u8] {
self.instance.inspect_memory(memory_index, address, len)
}
/// Shows the value of a global variable.
pub fn inspect_global(&self, global_index: GlobalIndex) -> &VMGlobal {
self.instance.inspect_global(global_index)
}
}

View File

@@ -3,4 +3,3 @@ for writing out native object files, using the wasm ABI defined by
[`wasmtime-environ`].
[`wasmtime-environ`]: https://crates.io/crates/wasmtime-environ

View File

@@ -12,8 +12,11 @@ readme = "README.md"
[dependencies]
cranelift-codegen = { git = "https://github.com/sunfishcode/cranelift.git", branch = "guard-offset" }
cranelift-native = { git = "https://github.com/sunfishcode/cranelift.git", branch = "guard-offset" }
cranelift-wasm = { git = "https://github.com/sunfishcode/cranelift.git", branch = "guard-offset" }
wasmtime-execute = { path = "../execute" }
wasmtime-environ = { path = "../environ" }
wabt = "0.7"
target-lexicon = "0.2.0"
[badges]
maintenance = { status = "experimental" }

View File

@@ -94,8 +94,7 @@ fn ignore(testsuite: &str, name: &str) -> bool {
match testsuite {
"spec_testsuite" => match name {
// These are the remaining spec testsuite failures.
"data" | "elem" | "exports" | "func" | "func_ptrs" | "globals" | "imports"
| "linking" | "names" | "start" => true,
"data" | "elem" | "imports" | "linking" => true,
_ => false,
},
_ => false,

View File

@@ -23,9 +23,13 @@
)]
extern crate cranelift_codegen;
extern crate cranelift_wasm;
extern crate target_lexicon;
extern crate wabt;
extern crate wasmtime_environ;
extern crate wasmtime_execute;
mod spectest;
mod wast;
pub use wast::{wast_buffer, wast_file};

210
lib/wast/src/spectest.rs Normal file
View File

@@ -0,0 +1,210 @@
use cranelift_codegen::ir::types;
use cranelift_codegen::{ir, isa};
use cranelift_wasm::{Global, GlobalInit, Memory, Table, TableElementType};
use std::ptr;
use target_lexicon::HOST;
use wasmtime_environ::{translate_signature, MemoryPlan, MemoryStyle, TablePlan, TableStyle};
use wasmtime_execute::{ExportValue, Resolver, VMGlobal, VMMemory, VMTable};
extern "C" fn spectest_print() {}
extern "C" fn spectest_print_i32(x: i32) {
println!("{}: i32", x);
}
extern "C" fn spectest_print_i64(x: i64) {
println!("{}: i64", x);
}
extern "C" fn spectest_print_f32(x: f32) {
println!("{}: f32", x);
}
extern "C" fn spectest_print_f64(x: f64) {
println!("{}: f64", x);
}
extern "C" fn spectest_print_i32_f32(x: i32, y: f32) {
println!("{}: i32", x);
println!("{}: f32", y);
}
extern "C" fn spectest_print_f64_f64(x: f64, y: f64) {
println!("{}: f64", x);
println!("{}: f64", y);
}
pub struct SpecTest {
spectest_global_i32: VMGlobal,
spectest_global_f32: VMGlobal,
spectest_global_f64: VMGlobal,
spectest_table: VMTable,
spectest_memory: VMMemory,
}
impl SpecTest {
pub fn new() -> Self {
Self {
spectest_global_i32: VMGlobal::definition(&Global {
ty: types::I32,
mutability: false,
initializer: GlobalInit::I32Const(0),
}),
spectest_global_f32: VMGlobal::definition(&Global {
ty: types::I32,
mutability: false,
initializer: GlobalInit::F32Const(0),
}),
spectest_global_f64: VMGlobal::definition(&Global {
ty: types::I32,
mutability: false,
initializer: GlobalInit::F64Const(0),
}),
spectest_table: VMTable::definition(ptr::null_mut(), 0),
spectest_memory: VMMemory::definition(ptr::null_mut(), 0),
}
}
}
impl Resolver for SpecTest {
fn resolve(&mut self, module: &str, field: &str) -> Option<ExportValue> {
let call_conv = isa::CallConv::triple_default(&HOST);
let pointer_type = types::Type::triple_pointer_type(&HOST);
match module {
"spectest" => match field {
"print" => Some(ExportValue::function(
spectest_print as usize,
translate_signature(
ir::Signature {
params: vec![],
returns: vec![],
call_conv,
},
pointer_type,
),
)),
"print_i32" => Some(ExportValue::function(
spectest_print_i32 as usize,
translate_signature(
ir::Signature {
params: vec![ir::AbiParam::new(types::I32)],
returns: vec![],
call_conv,
},
pointer_type,
),
)),
"print_i64" => Some(ExportValue::function(
spectest_print_i64 as usize,
translate_signature(
ir::Signature {
params: vec![ir::AbiParam::new(types::I64)],
returns: vec![],
call_conv,
},
pointer_type,
),
)),
"print_f32" => Some(ExportValue::function(
spectest_print_f32 as usize,
translate_signature(
ir::Signature {
params: vec![ir::AbiParam::new(types::F32)],
returns: vec![],
call_conv,
},
pointer_type,
),
)),
"print_f64" => Some(ExportValue::function(
spectest_print_f64 as usize,
translate_signature(
ir::Signature {
params: vec![ir::AbiParam::new(types::F64)],
returns: vec![],
call_conv,
},
pointer_type,
),
)),
"print_i32_f32" => Some(ExportValue::function(
spectest_print_i32_f32 as usize,
translate_signature(
ir::Signature {
params: vec![
ir::AbiParam::new(types::I32),
ir::AbiParam::new(types::F32),
],
returns: vec![],
call_conv,
},
pointer_type,
),
)),
"print_f64_f64" => Some(ExportValue::function(
spectest_print_f64_f64 as usize,
translate_signature(
ir::Signature {
params: vec![
ir::AbiParam::new(types::F64),
ir::AbiParam::new(types::F64),
],
returns: vec![],
call_conv,
},
pointer_type,
),
)),
"global_i32" => Some(ExportValue::global(
&mut self.spectest_global_i32,
Global {
ty: ir::types::I32,
mutability: false,
initializer: GlobalInit::I32Const(0),
},
)),
"global_f32" => Some(ExportValue::global(
&mut self.spectest_global_f32,
Global {
ty: ir::types::F32,
mutability: false,
initializer: GlobalInit::F32Const(0),
},
)),
"global_f64" => Some(ExportValue::global(
&mut self.spectest_global_f64,
Global {
ty: ir::types::F64,
mutability: false,
initializer: GlobalInit::F64Const(0),
},
)),
"table" => Some(ExportValue::table(
&mut self.spectest_table,
TablePlan {
table: Table {
ty: TableElementType::Func,
minimum: 0,
maximum: None,
},
style: TableStyle::CallerChecksSignature,
},
)),
"memory" => Some(ExportValue::memory(
&mut self.spectest_memory,
MemoryPlan {
memory: Memory {
minimum: 0,
maximum: None,
shared: false,
},
style: MemoryStyle::Dynamic,
offset_guard_size: 0,
},
)),
_ => None,
},
_ => None,
}
}
}

View File

@@ -1,4 +1,5 @@
use cranelift_codegen::isa;
use spectest::SpecTest;
use std::collections::HashMap;
use std::fs;
use std::io;
@@ -12,6 +13,7 @@ struct Instances {
current: Option<InstanceWorld>,
namespace: HashMap<String, InstanceWorld>,
code: Code,
spectest: SpecTest,
}
impl Instances {
@@ -20,11 +22,12 @@ impl Instances {
current: None,
namespace: HashMap::new(),
code: Code::new(),
spectest: SpecTest::new(),
}
}
fn instantiate(&mut self, isa: &isa::TargetIsa, module: ModuleBinary) -> InstanceWorld {
InstanceWorld::new(&mut self.code, isa, &module.into_vec()).unwrap()
InstanceWorld::new(&mut self.code, isa, &module.into_vec(), &mut self.spectest).unwrap()
}
pub fn define_unnamed_module(&mut self, isa: &isa::TargetIsa, module: ModuleBinary) {
@@ -41,6 +44,7 @@ impl Instances {
self.namespace.insert(name, world);
}
// fixme: Rename InvokeOutcome to ActionOutcome.
pub fn perform_action(&mut self, isa: &isa::TargetIsa, action: Action) -> InvokeOutcome {
match action {
Action::Invoke {
@@ -72,7 +76,25 @@ impl Instances {
.expect(&format!("error invoking {} in module {}", field, name)),
}
}
_ => panic!("unsupported action {:?}", action),
Action::Get { module, field } => {
let value = match module {
None => match self.current {
None => panic!("get performed with no module present"),
Some(ref mut instance_world) => instance_world
.get(&field)
.expect(&format!("error getting {} in current module", field)),
},
Some(name) => self
.namespace
.get_mut(&name)
.expect(&format!("module {} not declared", name))
.get(&field)
.expect(&format!("error getting {} in module {}", field, name)),
};
InvokeOutcome::Returned {
values: vec![value],
}
}
}
}
}

View File

@@ -35,7 +35,6 @@ extern crate cranelift_entity;
extern crate cranelift_native;
extern crate cranelift_wasm;
extern crate docopt;
extern crate wasmtime_environ;
extern crate wasmtime_execute;
#[macro_use]
extern crate serde_derive;
@@ -57,8 +56,7 @@ use std::io::stdout;
use std::path::Path;
use std::path::PathBuf;
use std::process::exit;
use wasmtime_environ::{Module, ModuleEnvironment, Tunables};
use wasmtime_execute::{compile_and_link_module, finish_instantiation, invoke, Code, Instance};
use wasmtime_execute::{Code, InstanceWorld, NullResolver};
static LOG_FILENAME_PREFIX: &str = "cranelift.dbg.";
@@ -147,51 +145,14 @@ fn handle_module(args: &Args, path: PathBuf, isa: &TargetIsa) -> Result<(), Stri
if !data.starts_with(&[b'\0', b'a', b's', b'm']) {
data = wabt::wat2wasm(data).map_err(|err| String::from(err.description()))?;
}
let mut module = Module::new();
// TODO: Expose the tunables as command-line flags.
let tunables = Tunables::default();
let environ = ModuleEnvironment::new(isa, &mut module, tunables);
let imports_resolver = |_env: &str, _function: &str| None;
let translation = environ.translate(&data).map_err(|e| e.to_string())?;
let mut resolver = NullResolver {};
let mut code = Code::new();
let instance = match compile_and_link_module(isa, &translation, &imports_resolver) {
Ok(compilation) => {
let mut instance = Instance::new(
translation.module,
&compilation,
&translation.lazy.data_initializers,
)?;
finish_instantiation(
&mut code,
isa,
&translation.module,
&compilation,
&mut instance,
)?;
let mut world = InstanceWorld::new(&mut code, isa, &data, &mut resolver)?;
if let Some(ref f) = args.flag_function {
invoke(
&mut code,
isa,
&translation.module,
&compilation,
instance.vmctx(),
&f,
&[],
)?;
world.invoke(&mut code, isa, &f, &[])?;
}
instance
}
Err(s) => {
return Err(s);
}
};
if args.flag_memory {
let mut input = String::new();
println!("Inspecting memory");
@@ -210,7 +171,7 @@ fn handle_module(args: &Args, path: PathBuf, isa: &TargetIsa) -> Result<(), Stri
if split.len() != 3 {
break;
}
let memory = instance.inspect_memory(
let memory = world.inspect_memory(
MemoryIndex::new(str::parse(split[0]).unwrap()),
str::parse(split[1]).unwrap(),
str::parse(split[2]).unwrap(),
@@ -235,7 +196,7 @@ mod tests {
use cranelift_codegen::settings::Configurable;
use std::path::PathBuf;
use wabt;
use wasmtime_environ::{Module, ModuleEnvironment, Tunables};
use wasmtime_execute::{Code, InstanceWorld, NullResolver};
const PATH_MODULE_RS2WASM_ADD_FUNC: &str = r"filetests/rs2wasm-add-func.wat";
@@ -257,11 +218,9 @@ mod tests {
});
let isa = isa_builder.finish(settings::Flags::new(flag_builder));
let mut module = Module::new();
let tunables = Tunables::default();
let environ = ModuleEnvironment::new(&*isa, &mut module, tunables);
let translation = environ.translate(&data);
assert!(translation.is_ok());
let mut resolver = NullResolver {};
let mut code = Code::new();
let world = InstanceWorld::new(&mut code, &*isa, &data, &mut resolver);
assert!(world.is_ok());
}
}