Support imported functions

This commit is contained in:
Lachlan Sneff
2019-01-08 21:57:28 -05:00
parent 55b7cae523
commit bba168e61e
14 changed files with 265 additions and 151 deletions

View File

@ -1,18 +1,4 @@
use wasmer_runtime::{
FuncResolver,
LinearMemory,
ModuleInner as WasmerModule,
SigRegistry,
module::{DataInitializer, Export, ImportName, TableInitializer},
types::{
ElementType as WasmerElementType, FuncIndex as WasmerFuncIndex, FuncSig as WasmerSignature,
Global as WasmerGlobal, GlobalDesc as WasmerGlobalDesc, GlobalIndex as WasmerGlobalIndex,
Initializer as WasmerInitializer, Map, MapIndex, Memory as WasmerMemory,
MemoryIndex as WasmerMemoryIndex, SigIndex as WasmerSignatureIndex, Table as WasmerTable,
TableIndex as WasmerTableIndex, Type as WasmerType,
},
vm::Ctx as WasmerVMContext,
};
use crate::resolver::FuncResolverBuilder;
use cranelift_codegen::cursor::FuncCursor;
use cranelift_codegen::ir::immediates::{Offset32, Uimm64};
use cranelift_codegen::ir::types::{self, *};
@ -29,7 +15,18 @@ use cranelift_wasm::{
};
use hashbrown::HashMap;
use target_lexicon;
use crate::resolver::{FuncResolverBuilder};
use wasmer_runtime::{
module::{DataInitializer, Export, ImportName, TableInitializer},
types::{
ElementType as WasmerElementType, FuncIndex as WasmerFuncIndex, FuncSig as WasmerSignature,
Global as WasmerGlobal, GlobalDesc as WasmerGlobalDesc, GlobalIndex as WasmerGlobalIndex,
Initializer as WasmerInitializer, Map, MapIndex, Memory as WasmerMemory,
MemoryIndex as WasmerMemoryIndex, SigIndex as WasmerSignatureIndex, Table as WasmerTable,
TableIndex as WasmerTableIndex, Type as WasmerType,
},
vm::{self, Ctx as WasmerVMContext},
LinearMemory, ModuleInner as WasmerModule, SigRegistry,
};
/// The converter namespace contains functions for converting a Cranelift module
/// to a Wasmer module.
@ -72,8 +69,17 @@ pub mod converter {
func_assoc.push(WasmerSignatureIndex::new(signature_index.index()));
}
let function_bodies: Vec<_> = cranelift_module.function_bodies.into_iter().map(|(_, v)| v.clone()).collect();
let func_resolver_builder = FuncResolverBuilder::new(&*crate::get_isa(), function_bodies).unwrap();
let function_bodies: Vec<_> = cranelift_module
.function_bodies
.into_iter()
.map(|(_, v)| v.clone())
.collect();
let func_resolver_builder = FuncResolverBuilder::new(
&*crate::get_isa(),
function_bodies,
cranelift_module.imported_functions.len(),
)
.unwrap();
// Create func_resolver.
let func_resolver = Box::new(func_resolver_builder.finalize().unwrap());
@ -217,9 +223,6 @@ pub struct CraneliftModule {
/// The external function declaration for implementing wasm's `grow_memory`.
pub grow_memory_extfunc: Option<FuncRef>,
/// A function that takes a Wasmer module and resolves a function index to a vm::Func.
pub func_resolver: Option<Box<dyn FuncResolver>>,
// An array holding information about the wasm instance memories.
pub memories: Vec<Memory>,
@ -271,7 +274,6 @@ impl CraneliftModule {
memories_base: None,
current_memory_extfunc: None,
grow_memory_extfunc: None,
func_resolver: None,
memories: Vec::new(),
globals: Vec::new(),
tables: Vec::new(),
@ -286,8 +288,7 @@ impl CraneliftModule {
};
// Translate wasm to cranelift IR.
translate_module(&buffer_source, &mut cranelift_module)
.map_err(|e| e.to_string())?;
translate_module(&buffer_source, &mut cranelift_module).map_err(|e| e.to_string())?;
// Return translated module.
Ok(cranelift_module)
@ -560,12 +561,68 @@ impl<'environment> FuncEnvironmentTrait for FuncEnvironment<'environment> {
fn translate_call(
&mut self,
mut pos: FuncCursor,
_callee_index: FuncIndex,
callee_index: FuncIndex,
callee: ir::FuncRef,
call_args: &[ir::Value],
) -> WasmResult<ir::Inst> {
// Insert call instructions for `callee`.
Ok(pos.ins().call(callee, call_args))
if callee_index.index() < self.module.imported_functions.len() {
// this is an imported function
let vmctx = pos.func.create_global_value(ir::GlobalValueData::VMContext);
let imported_funcs = pos.func.create_global_value(ir::GlobalValueData::Load {
base: vmctx,
offset: (WasmerVMContext::offset_imported_funcs() as i32).into(),
global_type: self.pointer_type(),
readonly: true,
});
let imported_func_struct_addr =
pos.func.create_global_value(ir::GlobalValueData::IAddImm {
base: imported_funcs,
offset: (callee_index.index() as i64 * vm::ImportedFunc::size() as i64).into(),
global_type: self.pointer_type(),
});
let imported_func_addr = pos.func.create_global_value(ir::GlobalValueData::Load {
base: imported_func_struct_addr,
offset: (vm::ImportedFunc::offset_func() as i32).into(),
global_type: self.pointer_type(),
readonly: true,
});
let imported_func_addr = pos
.ins()
.global_value(self.pointer_type(), imported_func_addr);
let sig_ref = pos.func.dfg.ext_funcs[callee].signature;
let vmctx = pos
.func
.special_param(ir::ArgumentPurpose::VMContext)
.expect("missing vmctx parameter");
let mut args = Vec::with_capacity(call_args.len() + 1);
args.extend(call_args.iter().cloned());
args.push(vmctx);
Ok(pos
.ins()
.call_indirect(sig_ref, imported_func_addr, &args[..]))
} else {
// this is an internal function
let vmctx = pos
.func
.special_param(ir::ArgumentPurpose::VMContext)
.expect("missing vmctx parameter");
let mut args = Vec::with_capacity(call_args.len() + 1);
args.extend(call_args.iter().cloned());
args.push(vmctx);
Ok(pos.ins().call(callee, &args[..]))
}
}
/// Generates code corresponding to wasm `memory.grow`.

View File

@ -3,13 +3,16 @@ mod libcalls;
mod relocation;
mod resolver;
use wasmer_runtime::{Compiler, Module};
use cranelift_codegen::{settings::{self, Configurable}, isa};
use cranelift_codegen::{
isa,
settings::{self, Configurable},
};
use target_lexicon::Triple;
use wasmer_runtime::{Compiler, Module};
use wasmparser::{self, WasmDecoder};
use self::codegen::CraneliftModule;
use self::codegen::converter;
use self::codegen::CraneliftModule;
pub struct CraneliftCompiler {}
@ -59,10 +62,7 @@ fn validate(bytes: &[u8]) -> Result<(), String> {
match *state {
wasmparser::ParserState::EndWasm => return Ok(()),
wasmparser::ParserState::Error(err) => {
return Err(format!(
"Validation error: {}",
err.message
));
return Err(format!("Validation error: {}", err.message));
}
_ => (),
}

View File

@ -1,19 +1,14 @@
use cranelift_codegen::{
ir,
isa,
Context,
};
use crate::libcalls;
use crate::relocation::{Reloc, RelocSink, Relocation, RelocationType, TrapSink};
use cranelift_codegen::{ir, isa, Context};
use std::mem;
use std::ptr::{write_unaligned, NonNull};
use wasmer_runtime::{
self,
types::{Map, MapIndex, FuncIndex},
mmap::{Mmap, Protect},
vm,
vmcalls,
types::{FuncIndex, Map, MapIndex},
vm, vmcalls,
};
use crate::relocation::{Reloc, RelocSink, Relocation, RelocationType, TrapSink};
use crate::libcalls;
use std::ptr::{write_unaligned, NonNull};
use std::mem;
#[allow(dead_code)]
pub struct FuncResolverBuilder {
@ -23,7 +18,11 @@ pub struct FuncResolverBuilder {
}
impl FuncResolverBuilder {
pub fn new(isa: &isa::TargetIsa, function_bodies: Vec<ir::Function>) -> Result<Self, String> {
pub fn new(
isa: &isa::TargetIsa,
function_bodies: Vec<ir::Function>,
num_imported_funcs: usize,
) -> Result<Self, String> {
let mut compiled_functions: Vec<Vec<u8>> = Vec::with_capacity(function_bodies.len());
let mut relocations = Map::with_capacity(function_bodies.len());
let mut trap_sinks = Map::with_capacity(function_bodies.len());
@ -37,11 +36,8 @@ impl FuncResolverBuilder {
let mut reloc_sink = RelocSink::new();
let mut trap_sink = TrapSink::new();
ctx
.compile_and_emit(isa, &mut code_buf, &mut reloc_sink, &mut trap_sink)
.map_err(|e| {
format!("compile error: {}", e.to_string())
})?;
ctx.compile_and_emit(isa, &mut code_buf, &mut reloc_sink, &mut trap_sink)
.map_err(|e| format!("compile error: {}", e.to_string()))?;
ctx.clear();
// Round up each function's size to pointer alignment.
total_size += round_up(code_buf.len(), mem::size_of::<usize>());
@ -62,7 +58,8 @@ impl FuncResolverBuilder {
for compiled in compiled_functions.iter() {
let new_end = previous_end + round_up(compiled.len(), mem::size_of::<usize>());
unsafe {
memory.as_slice_mut()[previous_end..previous_end + compiled.len()].copy_from_slice(&compiled[..]);
memory.as_slice_mut()[previous_end..previous_end + compiled.len()]
.copy_from_slice(&compiled[..]);
}
map.push(previous_end);
previous_end = new_end;
@ -70,6 +67,7 @@ impl FuncResolverBuilder {
Ok(Self {
resolver: FuncResolver {
num_imported_funcs,
map,
memory,
},
@ -86,7 +84,10 @@ impl FuncResolverBuilder {
// This will always be an internal function
// because imported functions are not
// called in this way.
self.resolver.lookup(FuncIndex::new(func_index as _)).unwrap().as_ptr() as isize
self.resolver
.lookup(FuncIndex::new(func_index as _))
.unwrap()
.as_ptr() as isize
}
RelocationType::CurrentMemory => vmcalls::memory_size as isize,
RelocationType::GrowMemory => vmcalls::memory_grow_static as isize,
@ -135,7 +136,9 @@ impl FuncResolverBuilder {
}
unsafe {
self.resolver.memory.protect(0..self.resolver.memory.size(), Protect::ReadExec)?;
self.resolver
.memory
.protect(0..self.resolver.memory.size(), Protect::ReadExec)?;
}
Ok(self.resolver)
@ -144,16 +147,17 @@ impl FuncResolverBuilder {
/// Resolves a function index to a function address.
pub struct FuncResolver {
num_imported_funcs: usize,
map: Map<FuncIndex, usize>,
memory: Mmap,
}
impl FuncResolver {
fn lookup(&self, index: FuncIndex) -> Option<NonNull<vm::Func>> {
let offset = *self.map.get(index)?;
let ptr = unsafe {
self.memory.as_ptr().add(offset)
};
let offset = *self
.map
.get(FuncIndex::new(index.index() - self.num_imported_funcs))?;
let ptr = unsafe { self.memory.as_ptr().add(offset) };
NonNull::new(ptr).map(|nonnull| nonnull.cast())
}

View File

@ -1,13 +1,38 @@
use wasmer_runtime as runtime;
use wasmer_clif_backend::CraneliftCompiler;
use wasmer_runtime::{
self as runtime,
types::{FuncSig, Type, Value},
vm, Import, Imports,
};
static EXAMPLE_WASM: &'static [u8] = include_bytes!("simple.wasm");
fn main() {
let compiler = CraneliftCompiler::new();
let module = runtime::compile(EXAMPLE_WASM, &compiler).unwrap();
let imports = runtime::Imports::new();
let mut instance = module.instantiate(&imports).unwrap();
let ret = instance.call("main", &[runtime::types::Value::I32(42)]);
fn main() -> Result<(), String> {
let module = runtime::compile(EXAMPLE_WASM, &CraneliftCompiler::new())?;
let mut imports = Imports::new();
imports.add(
"env".to_string(),
"print_num".to_string(),
Import::Func(
print_num as _,
FuncSig {
params: vec![Type::I32],
returns: vec![Type::I32],
},
),
);
let mut instance = module.instantiate(&imports)?;
let ret = instance.call("main", &[Value::I32(42)])?;
println!("ret: {:?}", ret);
Ok(())
}
extern "C" fn print_num(n: i32, _vmctx: *mut vm::Ctx) -> i32 {
println!("print_num({})", n);
n + 1
}

View File

@ -108,7 +108,6 @@ impl LocalBacking {
match table.elements {
TableElements::Anyfunc(ref mut elements) => {
for (i, &func_index) in init.elements.iter().enumerate() {
let sig_index = module.func_assoc[func_index];
let vm_sig_id = vm::SigId(sig_index.index() as u32);
@ -263,7 +262,8 @@ impl ImportBacking {
let sig_index = module.func_assoc[index];
let expected_sig = module.sig_registry.lookup_func_sig(sig_index);
let import = imports.get(mod_name, item_name);
if let Some(&Import::Func(func, ref signature)) = import {
match import {
Some(&Import::Func(func, ref signature)) => {
if expected_sig == signature {
functions.push(vm::ImportedFunc {
func,
@ -275,8 +275,16 @@ impl ImportBacking {
mod_name, item_name
));
}
} else {
return Err(format!("incorrect type for {:?}:{:?}", mod_name, item_name));
}
Some(_) => {
return Err(format!(
"incorrect import type for {}:{}",
mod_name, item_name
));
}
None => {
return Err(format!("import not found: {}:{}", mod_name, item_name));
}
}
}
@ -293,15 +301,16 @@ impl ImportBacking {
) in &module.imported_globals
{
let import = imports.get(mod_name, item_name);
if let Some(&Import::Global(val)) = import {
match import {
Some(Import::Global(val)) => {
if val.ty() == global_desc.ty {
globals.push(vm::ImportedGlobal {
global: vm::LocalGlobal {
data: match val {
Value::I32(n) => n as u64,
Value::I64(n) => n as u64,
Value::F32(n) => n as u64,
Value::F64(n) => n,
Value::I32(n) => *n as u64,
Value::I64(n) => *n as u64,
Value::F32(n) => *n as u64,
Value::F64(n) => *n,
},
},
});
@ -311,8 +320,16 @@ impl ImportBacking {
mod_name, item_name
));
}
} else {
return Err(format!("incorrect type for {:?}:{:?}", mod_name, item_name));
}
Some(_) => {
return Err(format!(
"incorrect import type for {}:{}",
mod_name, item_name
));
}
None => {
return Err(format!("import not found: {}:{}", mod_name, item_name));
}
}
}

View File

@ -19,7 +19,10 @@ pub struct Instance {
}
impl Instance {
pub(crate) fn new(module: Module, imports: &dyn ImportResolver) -> Result<Box<Instance>, String> {
pub(crate) fn new(
module: Module,
imports: &dyn ImportResolver,
) -> Result<Box<Instance>, String> {
let import_backing = ImportBacking::new(&module, imports)?;
let backing = LocalBacking::new(&module, &import_backing);
@ -85,10 +88,7 @@ impl Instance {
// the vmctx will be located at the same place on the stack the entire time that this
// wasm function is running.
let mut vmctx = vm::Ctx::new(
&mut self.backing,
&mut self.import_backing,
);
let mut vmctx = vm::Ctx::new(&mut self.backing, &mut self.import_backing);
let vmctx_ptr = &mut vmctx as *mut vm::Ctx;
let libffi_args: Vec<_> = args
@ -102,15 +102,19 @@ impl Instance {
.chain(iter::once(libffi_arg(&vmctx_ptr)))
.collect();
let func_ptr = CodePtr::from_ptr(self.module
let func_ptr = CodePtr::from_ptr(
self.module
.func_resolver
.get(&self.module, func_index)
.expect("broken invariant, func resolver not synced with module.exports")
.cast()
.as_ptr());
.as_ptr(),
);
call_protected(|| {
self.module.sig_registry.lookup_func_sig(sig_index)
self.module
.sig_registry
.lookup_func_sig(sig_index)
.returns
.first()
.map(|ty| match ty {

View File

@ -4,26 +4,23 @@ mod backend;
mod backing;
mod instance;
mod memory;
mod sig_registry;
mod table;
mod recovery;
mod sighandler;
pub mod mmap;
pub mod module;
mod recovery;
mod sig_registry;
mod sighandler;
mod table;
pub mod types;
pub mod vm;
pub mod vmcalls;
pub use self::backend::{Compiler, FuncResolver};
pub use self::instance::{Import, ImportResolver, Imports, Instance};
pub use self::memory::LinearMemory;
pub use self::module::{Module, ModuleInner};
pub use self::sig_registry::SigRegistry;
pub use self::memory::LinearMemory;
/// Compile a webassembly module using the provided compiler.
pub fn compile(
wasm: &[u8],
compiler: &dyn Compiler,
) -> Result<Module, String> {
pub fn compile(wasm: &[u8], compiler: &dyn Compiler) -> Result<Module, String> {
compiler.compile(wasm)
}

View File

@ -5,7 +5,11 @@
//! mutable from both Rust and WebAssembly.
use std::ops::{Deref, DerefMut};
use crate::{types::Memory, vm::LocalMemory, mmap::{Mmap, Protect}};
use crate::{
mmap::{Mmap, Protect},
types::Memory,
vm::LocalMemory,
};
/// A linear memory instance.
#[derive(Debug)]
@ -69,7 +73,10 @@ impl LinearMemory {
// map initial pages as readwrite since the inital mmap is mapped as not accessible.
if initial_pages != 0 {
unsafe {
mmap.protect(0..(initial_pages as usize * Self::PAGE_SIZE as usize), Protect::ReadWrite)
mmap.protect(
0..(initial_pages as usize * Self::PAGE_SIZE as usize),
Protect::ReadWrite,
)
.expect("unable to make memory accessible");
}
}
@ -191,7 +198,9 @@ impl LinearMemory {
let new_bytes = (new_pages * Self::PAGE_SIZE) as usize;
unsafe {
self.mmap.protect(prev_bytes..new_bytes, Protect::ReadWrite).ok()?;
self.mmap
.protect(prev_bytes..new_bytes, Protect::ReadWrite)
.ok()?;
}
self.current = new_pages;
@ -210,16 +219,12 @@ impl PartialEq for LinearMemory {
impl Deref for LinearMemory {
type Target = [u8];
fn deref(&self) -> &[u8] {
unsafe {
self.mmap.as_slice()
}
unsafe { self.mmap.as_slice() }
}
}
impl DerefMut for LinearMemory {
fn deref_mut(&mut self) -> &mut [u8] {
unsafe {
self.mmap.as_slice_mut()
}
unsafe { self.mmap.as_slice_mut() }
}
}

View File

@ -1,8 +1,8 @@
use std::{slice, ptr};
use std::ops::Range;
use errno;
use nix::libc;
use page_size;
use errno;
use std::ops::Range;
use std::{ptr, slice};
#[derive(Debug)]
pub struct Mmap {
@ -44,7 +44,9 @@ impl Mmap {
pub unsafe fn protect(&mut self, range: Range<usize>, protect: Protect) -> Result<(), String> {
let page_size = page_size::get();
let start = self.ptr.add(round_down_to_page_size(range.start, page_size));
let start = self
.ptr
.add(round_down_to_page_size(range.start, page_size));
let size = round_up_to_page_size(range.end - range.start, page_size);
assert!(size <= self.size);
@ -100,5 +102,5 @@ fn round_up_to_page_size(size: usize, page_size: usize) -> usize {
/// Round `size` down to the nearest multiple of `page_size`.
fn round_down_to_page_size(size: usize, page_size: usize) -> usize {
size & !(page_size-1)
size & !(page_size - 1)
}

View File

@ -1,16 +1,15 @@
use crate::{
backend::FuncResolver,
types::{
FuncIndex, Global, GlobalDesc, GlobalIndex, Map, MapIndex, Memory, MemoryIndex,
SigIndex, Table, TableIndex,
},
sig_registry::SigRegistry,
ImportResolver,
Instance,
types::{
FuncIndex, Global, GlobalDesc, GlobalIndex, Map, MapIndex, Memory, MemoryIndex, SigIndex,
Table, TableIndex,
},
ImportResolver, Instance,
};
use std::sync::Arc;
use std::ops::Deref;
use hashbrown::HashMap;
use std::ops::Deref;
use std::sync::Arc;
/// This is used to instantiate a new webassembly module.
pub struct ModuleInner {
@ -62,7 +61,6 @@ impl Deref for Module {
}
}
#[derive(Debug, Clone)]
pub struct ImportName {
pub module: String,

View File

@ -1,5 +1,5 @@
use crate::{
types::{FuncSig, Map, SigIndex, MapIndex},
types::{FuncSig, Map, MapIndex, SigIndex},
vm,
};
use hashbrown::HashMap;
@ -20,9 +20,9 @@ impl SigRegistry {
pub fn register(&mut self, func_sig: FuncSig) -> SigIndex {
let func_table = &mut self.func_table;
let sig_assoc = &mut self.sig_assoc;
*func_table.entry(func_sig.clone()).or_insert_with(|| {
sig_assoc.push(func_sig)
})
*func_table
.entry(func_sig.clone())
.or_insert_with(|| sig_assoc.push(func_sig))
}
pub fn lookup_func_sig(&self, sig_index: SigIndex) -> &FuncSig {
@ -30,7 +30,11 @@ impl SigRegistry {
}
pub(crate) fn into_vm_sigid(&self) -> Box<[vm::SigId]> {
let v: Vec<_> = self.sig_assoc.iter().map(|(sig_index, _)| vm::SigId(sig_index.index() as u32)).collect();
let v: Vec<_> = self
.sig_assoc
.iter()
.map(|(sig_index, _)| vm::SigId(sig_index.index() as u32))
.collect();
v.into_boxed_slice()
}
}

View File

@ -17,7 +17,7 @@ pub enum Type {
F64,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Value {
/// The `i32` type.
I32(i32),
@ -83,7 +83,7 @@ pub struct Table {
/// A global value initializer.
/// Overtime, this will be able to represent more and more
/// complex expressions.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Initializer {
/// Corresponds to a `const.*` instruction.
Const(Value),
@ -98,7 +98,7 @@ pub struct GlobalDesc {
}
/// A wasm global.
#[derive(Debug, Clone, Copy)]
#[derive(Debug, Clone)]
pub struct Global {
pub desc: GlobalDesc,
pub init: Initializer,

View File

@ -33,10 +33,7 @@ pub struct Ctx {
}
impl Ctx {
pub fn new(
local_backing: &mut LocalBacking,
import_backing: &mut ImportBacking,
) -> Self {
pub fn new(local_backing: &mut LocalBacking, import_backing: &mut ImportBacking) -> Self {
Self {
memories: local_backing.vm_memories.as_mut_ptr(),
tables: local_backing.vm_tables.as_mut_ptr(),
@ -101,6 +98,10 @@ impl ImportedFunc {
pub fn offset_func() -> u8 {
0 * (mem::size_of::<usize>() as u8)
}
pub fn size() -> u8 {
mem::size_of::<Self>() as u8
}
}
/// Definition of a table used by the VM. (obviously)