Skip to content

Commit

Permalink
Merge pull request #11 from SamWilsn/execcode
Browse files Browse the repository at this point in the history
Provide host functions for loading Wasm modules and calling to/from them
  • Loading branch information
lightclient authored Nov 10, 2019
2 parents afe5ed9 + 446e68d commit d53ff19
Show file tree
Hide file tree
Showing 13 changed files with 1,364 additions and 447 deletions.
4 changes: 4 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,7 @@ wabt = "0.9.2"
wasmi = "0.5.0"
log = "0.4.8"
arrayref = "0.3.5"
typed-builder = "0.3.0"

[dev-dependencies]
lazy_static = "1.4.0"
2 changes: 1 addition & 1 deletion src/buffer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use std::collections::HashMap;
type K = u8;
type V = HashMap<[u8; 32], [u8; 32]>;

#[derive(Clone, Default)]
#[derive(Debug, Clone, Default)]
pub struct Buffer {
map: HashMap<K, V>,
}
Expand Down
171 changes: 171 additions & 0 deletions src/env/child/mod.rs
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"),
}
}
}
44 changes: 44 additions & 0 deletions src/env/child/resolver.rs
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)
}
}
57 changes: 57 additions & 0 deletions src/env/mod.rs
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)
}
}
Loading

0 comments on commit d53ff19

Please sign in to comment.