-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #11 from SamWilsn/execcode
Provide host functions for loading Wasm modules and calling to/from them
- Loading branch information
Showing
13 changed files
with
1,364 additions
and
447 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,171 @@ | ||
mod resolver; | ||
|
||
use crate::env::root::{RootRuntime, RootRuntimeWeak}; | ||
|
||
use self::resolver::{externals, ChildModuleImportResolver}; | ||
|
||
use std::cell::RefCell; | ||
|
||
use super::{ExtResult, StackFrame}; | ||
|
||
use wasmi::{ | ||
Externals, FuncInstance, ImportsBuilder, MemoryRef, Module, ModuleInstance, ModuleRef, | ||
RuntimeArgs, RuntimeValue, Trap, | ||
}; | ||
|
||
#[derive(Debug)] | ||
pub struct ChildRuntime<'a> { | ||
instance: ModuleRef, | ||
root: RootRuntimeWeak<'a>, | ||
|
||
call_stack: RefCell<Vec<StackFrame>>, | ||
} | ||
|
||
impl<'a> ChildRuntime<'a> { | ||
pub(crate) fn new(root: RootRuntimeWeak<'a>, code: &[u8]) -> Self { | ||
let module = Module::from_buffer(code).expect("Module loading to succeed"); | ||
|
||
let mut imports = ImportsBuilder::new(); | ||
imports.push_resolver("env", &ChildModuleImportResolver); | ||
|
||
let instance = ModuleInstance::new(&module, &imports) | ||
.expect("Module instantation expected to succeed") | ||
.assert_no_start(); | ||
|
||
Self { | ||
instance, | ||
root, | ||
call_stack: Default::default(), | ||
} | ||
} | ||
|
||
pub(super) fn call(&self, name: &str, frame: StackFrame) -> i32 { | ||
let export = self | ||
.instance | ||
.export_by_name(name) | ||
.expect("name doesn't exist in child"); | ||
|
||
let func = export.as_func().expect("name isn't a function"); | ||
|
||
self.call_stack.borrow_mut().push(frame); | ||
|
||
let mut externals = ChildExternals(self); | ||
let result = FuncInstance::invoke(&func, &[], &mut externals) | ||
.expect("function provided by child runtime failed") | ||
.expect("function provided by child runtime did not return a value") | ||
.try_into() | ||
.expect("funtion provided by child runtime return a non-i32 value"); | ||
|
||
self.call_stack.borrow_mut().pop().unwrap(); | ||
|
||
result | ||
} | ||
|
||
fn memory(&self) -> MemoryRef { | ||
self.instance | ||
.export_by_name("memory") | ||
.expect("Module expected to have 'memory' export") | ||
.as_memory() | ||
.cloned() | ||
.expect("'memory' export should be a memory") | ||
} | ||
|
||
fn root(&self) -> RootRuntime<'a> { | ||
self.root | ||
.upgrade() | ||
.expect("root runtime dropped before child") | ||
} | ||
|
||
fn ext_call(&self, args: RuntimeArgs) -> ExtResult { | ||
let memory = self.memory(); | ||
|
||
let name_ptr: u32 = args.nth(0); | ||
let name_len: u32 = args.nth(1); | ||
let name_bytes = memory.get(name_ptr, name_len as usize).unwrap(); | ||
let name = String::from_utf8(name_bytes).unwrap(); | ||
|
||
let arg_ptr: u32 = args.nth(2); | ||
let arg_len: u32 = args.nth(3); | ||
|
||
let ret_ptr: u32 = args.nth(4); | ||
let ret_len: u32 = args.nth(5); | ||
|
||
let frame = StackFrame::builder() | ||
.argument_offset(arg_ptr) | ||
.argument_length(arg_len) | ||
.return_offset(ret_ptr) | ||
.return_length(ret_len) | ||
.memory(memory) | ||
.build(); | ||
|
||
let retcode = self.root().call(&name, frame); | ||
|
||
Ok(Some(retcode.into())) | ||
} | ||
|
||
/// Copies the argument data from the most recent call into memory at the | ||
/// given offtet and length. Returns the actual length of the argument data. | ||
/// | ||
/// # Signature | ||
/// | ||
/// ```text | ||
/// eth2_argument(dest_offset: u32, dest_length: u32) -> u32 | ||
/// ``` | ||
fn ext_argument(&self, args: RuntimeArgs) -> ExtResult { | ||
let memory = self.memory(); | ||
|
||
let dest_ptr: u32 = args.nth(0); | ||
let dest_len: u32 = args.nth(1); | ||
|
||
let call_stack = self.call_stack.borrow(); | ||
let top = call_stack | ||
.last() | ||
.expect("eth2_argument requires a call stack"); | ||
|
||
let len = top.transfer_argument(&memory, dest_ptr, dest_len).unwrap(); | ||
|
||
Ok(Some(len.into())) | ||
} | ||
|
||
/// Copies data from the given offset and length into the buffer allocated | ||
/// by the caller. Returns the total size of the caller's buffer. | ||
/// | ||
/// # Signature | ||
/// | ||
/// ```text | ||
/// eth2_return(offset: u32, length: u32) -> u32 | ||
/// ``` | ||
fn ext_return(&self, args: RuntimeArgs) -> ExtResult { | ||
let memory = self.memory(); | ||
|
||
let src_ptr: u32 = args.nth(0); | ||
let src_len: u32 = args.nth(1); | ||
|
||
let call_stack = self.call_stack.borrow(); | ||
let top = call_stack | ||
.last() | ||
.expect("eth2_return requires a call stack"); | ||
|
||
let len = top.transfer_return(&memory, src_ptr, src_len).unwrap(); | ||
|
||
Ok(Some(len.into())) | ||
} | ||
} | ||
|
||
#[derive(Debug)] | ||
struct ChildExternals<'a, 'b>(&'a ChildRuntime<'b>); | ||
|
||
impl<'a, 'b> Externals for ChildExternals<'a, 'b> { | ||
fn invoke_index( | ||
&mut self, | ||
index: usize, | ||
args: RuntimeArgs, | ||
) -> Result<Option<RuntimeValue>, Trap> { | ||
match index { | ||
externals::CALL => self.0.ext_call(args), | ||
externals::ARGUMENT => self.0.ext_argument(args), | ||
externals::RETURN => self.0.ext_return(args), | ||
_ => panic!("unknown function index"), | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
pub mod externals { | ||
pub const CALL: usize = 1; | ||
pub const ARGUMENT: usize = 2; | ||
pub const RETURN: usize = 3; | ||
} | ||
|
||
use wasmi::{ | ||
Error as InterpreterError, FuncInstance, FuncRef, ModuleImportResolver, Signature, ValueType, | ||
}; | ||
|
||
pub struct ChildModuleImportResolver; | ||
|
||
impl<'a> ModuleImportResolver for ChildModuleImportResolver { | ||
fn resolve_func( | ||
&self, | ||
field_name: &str, | ||
_signature: &Signature, | ||
) -> Result<FuncRef, InterpreterError> { | ||
let func_ref = match field_name { | ||
"eth2_return" => FuncInstance::alloc_host( | ||
// eth2_return(offset: u32, length: u32) -> u32 | ||
Signature::new(&[ValueType::I32; 2][..], Some(ValueType::I32)), | ||
externals::RETURN, | ||
), | ||
"eth2_argument" => FuncInstance::alloc_host( | ||
// eth2_argument(offset: u32, length: u32) -> u32 | ||
Signature::new(&[ValueType::I32; 2][..], Some(ValueType::I32)), | ||
externals::ARGUMENT, | ||
), | ||
"eth2_call" => FuncInstance::alloc_host( | ||
// eth2_call(name, name_len, arg, arg_len, ret, ret_len) | ||
Signature::new(&[ValueType::I32; 6][..], Some(ValueType::I32)), | ||
externals::CALL, | ||
), | ||
_ => { | ||
return Err(InterpreterError::Function(format!( | ||
"host module doesn't export function with name {}", | ||
field_name | ||
))) | ||
} | ||
}; | ||
Ok(func_ref) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
pub mod child; | ||
pub mod root; | ||
|
||
use typed_builder::TypedBuilder; | ||
|
||
use wasmi::{MemoryInstance, MemoryRef, RuntimeValue, Trap}; | ||
|
||
pub type ExtResult = Result<Option<RuntimeValue>, Trap>; | ||
|
||
#[derive(Debug, Clone, TypedBuilder)] | ||
struct StackFrame { | ||
memory: MemoryRef, | ||
|
||
argument_offset: u32, | ||
argument_length: u32, | ||
|
||
return_offset: u32, | ||
return_length: u32, | ||
} | ||
|
||
impl StackFrame { | ||
pub fn transfer_argument( | ||
&self, | ||
dest: &MemoryRef, | ||
dest_ptr: u32, | ||
dest_len: u32, | ||
) -> Result<u32, wasmi::Error> { | ||
let len = std::cmp::min(dest_len, self.argument_length); | ||
|
||
MemoryInstance::transfer( | ||
&self.memory, | ||
self.argument_offset as usize, | ||
dest, | ||
dest_ptr as usize, | ||
len as usize, | ||
) | ||
.map(|_| self.argument_length) | ||
} | ||
|
||
pub fn transfer_return( | ||
&self, | ||
src: &MemoryRef, | ||
src_ptr: u32, | ||
src_len: u32, | ||
) -> Result<u32, wasmi::Error> { | ||
let len = std::cmp::min(src_len, self.return_length); | ||
|
||
MemoryInstance::transfer( | ||
src, | ||
src_ptr as usize, | ||
&self.memory, | ||
self.return_offset as usize, | ||
len as usize, | ||
) | ||
.map(|_| self.return_length) | ||
} | ||
} |
Oops, something went wrong.