From fc9c109fba44629f6f5342ad44a0abeed5eae4a1 Mon Sep 17 00:00:00 2001 From: Sam Wilson Date: Tue, 5 Nov 2019 16:43:21 -0500 Subject: [PATCH 1/7] Stub host function --- src/resolver.rs | 5 +++++ src/runtime.rs | 12 +++++++++++- tests/host_functions.rs | 38 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 54 insertions(+), 1 deletion(-) diff --git a/src/resolver.rs b/src/resolver.rs index d9146af..dc5a089 100644 --- a/src/resolver.rs +++ b/src/resolver.rs @@ -10,6 +10,7 @@ pub const BUFFERGET_FUNC_INDEX: usize = 4; pub const BUFFERSET_FUNC_INDEX: usize = 5; pub const BUFFERMERGE_FUNC_INDEX: usize = 6; pub const BUFFERCLEAR_FUNC_INDEX: usize = 7; +pub const EXEC_FUNC_INDEX: usize = 8; pub struct RuntimeModuleImportResolver; @@ -55,6 +56,10 @@ impl<'a> ModuleImportResolver for RuntimeModuleImportResolver { Signature::new(&[ValueType::I32][..], None), BUFFERCLEAR_FUNC_INDEX, ), + "eth2_exec" => FuncInstance::alloc_host( + Signature::new(&[ValueType::I32, ValueType::I32][..], None), + EXEC_FUNC_INDEX, + ), _ => { return Err(InterpreterError::Function(format!( "host module doesn't export function with name {}", diff --git a/src/runtime.rs b/src/runtime.rs index e903321..7c8e204 100644 --- a/src/runtime.rs +++ b/src/runtime.rs @@ -5,7 +5,7 @@ use crate::execute::Execute; use crate::resolver::{ RuntimeModuleImportResolver, BLOCKDATACOPY_FUNC_INDEX, BLOCKDATASIZE_FUNC_INDEX, BUFFERCLEAR_FUNC_INDEX, BUFFERGET_FUNC_INDEX, BUFFERMERGE_FUNC_INDEX, BUFFERSET_FUNC_INDEX, - LOADPRESTATEROOT_FUNC_INDEX, SAVEPOSTSTATEROOT_FUNC_INDEX, + EXEC_FUNC_INDEX, LOADPRESTATEROOT_FUNC_INDEX, SAVEPOSTSTATEROOT_FUNC_INDEX, }; use log::debug; @@ -177,6 +177,15 @@ impl<'a> Runtime<'a> { Ok(None) } + + fn ext_exec(&mut self, args: RuntimeArgs) -> ExtResult { + let code_ptr: u32 = args.nth(0); + let code_len: u32 = args.nth(1); + + debug!("exec 0x{:x} ({} bytes)", code_ptr, code_len); + + unimplemented!() + } } impl<'a> Execute<'a> for Runtime<'a> { @@ -221,6 +230,7 @@ impl<'a> Externals for Runtime<'a> { BUFFERSET_FUNC_INDEX => self.ext_buffer_set(args), BUFFERMERGE_FUNC_INDEX => self.ext_buffer_merge(args), BUFFERCLEAR_FUNC_INDEX => self.ext_buffer_clear(args), + EXEC_FUNC_INDEX => self.ext_exec(args), _ => panic!("unknown function index"), } } diff --git a/tests/host_functions.rs b/tests/host_functions.rs index 969c994..8b04ca5 100644 --- a/tests/host_functions.rs +++ b/tests/host_functions.rs @@ -1,6 +1,20 @@ use ewasm::{Execute, Runtime}; use wabt::wat2wasm; +fn nop() -> Vec { + wat2wasm(r#"(module (func $main (export "main") (nop)))"#).unwrap() +} + +fn escape(bytes: &[u8]) -> String { + let mut output = String::with_capacity(bytes.len() * 4); + + for byte in bytes { + output.push_str(&format!(r#"\{:02x}"#, byte)); + } + + output +} + fn compile_wat(code: &str) -> Vec { wat2wasm( [ @@ -14,6 +28,7 @@ fn compile_wat(code: &str) -> Vec { (import "env" "eth2_bufferSet" (func $buffer_set (param i32) (param i32) (param i32))) (import "env" "eth2_bufferMerge" (func $buffer_merge (param i32) (param i32))) (import "env" "eth2_bufferClear" (func $buffer_clear (param i32))) + (import "env" "eth2_exec" (func $exec (param i32) (param i32))) (memory (export "memory") 1) (func $main (export "main") "#, @@ -31,6 +46,29 @@ fn build_root(n: u8) -> [u8; 32] { ret } +#[test] +#[should_panic] +fn exec() { + let child_code = nop(); + + let code = wat2wasm(format!( + r#" + (module + (import "env" "eth2_exec" (func $exec (param i32) (param i32))) + (memory (export "memory") 1) + (data (i32.const 0) "{}") + (func $main (export "main") + (call $exec (i32.const 0) (i32.const {})) + ))"#, + escape(&child_code), + child_code.len(), + )) + .unwrap(); + + let mut runtime = Runtime::new(&code, &[], [0u8; 32]); + runtime.execute(); +} + #[test] fn save_post_root() { let code = compile_wat( From 1c18cb7602f9f1e16b8e7c17f9344d0466625724 Mon Sep 17 00:00:00 2001 From: Sam Wilson Date: Tue, 5 Nov 2019 16:49:39 -0500 Subject: [PATCH 2/7] Differentiate between root and non-root runtimes --- src/env/mod.rs | 1 + src/{runtime.rs => env/root/mod.rs} | 25 ++++++++++++++----------- src/{ => env/root}/resolver.rs | 0 src/lib.rs | 5 ++--- tests/host_functions.rs | 18 +++++++++--------- 5 files changed, 26 insertions(+), 23 deletions(-) create mode 100644 src/env/mod.rs rename src/{runtime.rs => env/root/mod.rs} (97%) rename src/{ => env/root}/resolver.rs (100%) diff --git a/src/env/mod.rs b/src/env/mod.rs new file mode 100644 index 0000000..dec16f3 --- /dev/null +++ b/src/env/mod.rs @@ -0,0 +1 @@ +pub mod root; diff --git a/src/runtime.rs b/src/env/root/mod.rs similarity index 97% rename from src/runtime.rs rename to src/env/root/mod.rs index 7c8e204..be02cb7 100644 --- a/src/runtime.rs +++ b/src/env/root/mod.rs @@ -1,15 +1,18 @@ +mod resolver; + use arrayref::array_ref; use crate::buffer::Buffer; use crate::execute::Execute; -use crate::resolver::{ + +use log::debug; + +use self::resolver::{ RuntimeModuleImportResolver, BLOCKDATACOPY_FUNC_INDEX, BLOCKDATASIZE_FUNC_INDEX, BUFFERCLEAR_FUNC_INDEX, BUFFERGET_FUNC_INDEX, BUFFERMERGE_FUNC_INDEX, BUFFERSET_FUNC_INDEX, EXEC_FUNC_INDEX, LOADPRESTATEROOT_FUNC_INDEX, SAVEPOSTSTATEROOT_FUNC_INDEX, }; -use log::debug; - use wasmi::{ Externals, ImportsBuilder, MemoryRef, Module, ModuleInstance, RuntimeArgs, RuntimeValue, Trap, }; @@ -17,7 +20,7 @@ use wasmi::{ pub type ExtResult = Result, Trap>; #[derive(Clone)] -pub struct Runtime<'a> { +pub struct RootRuntime<'a> { pub(crate) code: &'a [u8], pub(crate) data: &'a [u8], pub(crate) pre_root: [u8; 32], @@ -26,9 +29,9 @@ pub struct Runtime<'a> { pub(crate) buffer: Buffer, } -impl<'a> Runtime<'a> { - pub fn new(code: &'a [u8], data: &'a [u8], pre_root: [u8; 32]) -> Runtime<'a> { - Runtime { +impl<'a> RootRuntime<'a> { + pub fn new(code: &'a [u8], data: &'a [u8], pre_root: [u8; 32]) -> RootRuntime<'a> { + RootRuntime { code, data, pre_root, @@ -188,7 +191,7 @@ impl<'a> Runtime<'a> { } } -impl<'a> Execute<'a> for Runtime<'a> { +impl<'a> Execute<'a> for RootRuntime<'a> { fn execute(&'a mut self) -> [u8; 32] { let module = Module::from_buffer(self.code).expect("Module loading to succeed"); let mut imports = ImportsBuilder::new(); @@ -215,7 +218,7 @@ impl<'a> Execute<'a> for Runtime<'a> { } } -impl<'a> Externals for Runtime<'a> { +impl<'a> Externals for RootRuntime<'a> { fn invoke_index( &mut self, index: usize, @@ -254,8 +257,8 @@ mod test { pre_root: [u8; 32], memory: MemoryRef, buffer: Buffer, - ) -> Runtime<'a> { - Runtime { + ) -> RootRuntime<'a> { + RootRuntime { code: &[], data: data, pre_root, diff --git a/src/resolver.rs b/src/env/root/resolver.rs similarity index 100% rename from src/resolver.rs rename to src/env/root/resolver.rs diff --git a/src/lib.rs b/src/lib.rs index e74f088..208887a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,7 +1,6 @@ mod buffer; mod execute; -mod resolver; -mod runtime; +mod env; pub use execute::Execute; -pub use runtime::Runtime; +pub use env::root::RootRuntime; diff --git a/tests/host_functions.rs b/tests/host_functions.rs index 8b04ca5..1337930 100644 --- a/tests/host_functions.rs +++ b/tests/host_functions.rs @@ -1,4 +1,4 @@ -use ewasm::{Execute, Runtime}; +use ewasm::{Execute, RootRuntime}; use wabt::wat2wasm; fn nop() -> Vec { @@ -65,7 +65,7 @@ fn exec() { )) .unwrap(); - let mut runtime = Runtime::new(&code, &[], [0u8; 32]); + let mut runtime = RootRuntime::new(&code, &[], [0u8; 32]); runtime.execute(); } @@ -78,7 +78,7 @@ fn save_post_root() { "#, ); - let mut runtime = Runtime::new(&code, &[], [0u8; 32]); + let mut runtime = RootRuntime::new(&code, &[], [0u8; 32]); let post_root = runtime.execute(); assert_eq!(post_root, build_root(42)); } @@ -92,7 +92,7 @@ fn load_pre_root() { "#, ); - let mut runtime = Runtime::new(&code, &[], build_root(42)); + let mut runtime = RootRuntime::new(&code, &[], build_root(42)); let post_root = runtime.execute(); assert_eq!(post_root, build_root(42)); } @@ -107,7 +107,7 @@ fn block_data_size() { "#, ); - let mut runtime = Runtime::new(&code, &[0u8; 42], build_root(42)); + let mut runtime = RootRuntime::new(&code, &[0u8; 42], build_root(42)); let post_root = runtime.execute(); assert_eq!(post_root, build_root(42)); } @@ -122,7 +122,7 @@ fn block_data_copy() { ); let block_data = build_root(42); - let mut runtime = Runtime::new(&code, &block_data, [0u8; 32]); + let mut runtime = RootRuntime::new(&code, &block_data, [0u8; 32]); let post_root = runtime.execute(); assert_eq!(post_root, build_root(42)); } @@ -139,7 +139,7 @@ fn buffer_get_and_set() { "#, ); - let mut runtime = Runtime::new(&code, &[], [0u8; 32]); + let mut runtime = RootRuntime::new(&code, &[], [0u8; 32]); let post_root = runtime.execute(); assert_eq!(post_root, build_root(42)); } @@ -188,7 +188,7 @@ fn buffer_merge() { "#, ); - let mut runtime = Runtime::new(&code, &[], [0u8; 32]); + let mut runtime = RootRuntime::new(&code, &[], [0u8; 32]); let post_root = runtime.execute(); // The post root should be 1 + 3 + 4 = 8 @@ -226,7 +226,7 @@ fn buffer_clear() { "#, ); - let mut runtime = Runtime::new(&code, &[], [0u8; 32]); + let mut runtime = RootRuntime::new(&code, &[], [0u8; 32]); let post_root = runtime.execute(); // The post root should be 2 - 0 = 2 From 3c1b760c4c398e0d6923356e4ff3dfbf03c3c8d9 Mon Sep 17 00:00:00 2001 From: Sam Wilson Date: Tue, 5 Nov 2019 17:17:08 -0500 Subject: [PATCH 3/7] Create a separate runtime for sub-executions --- src/env/child/mod.rs | 46 +++++++++++++++++++++++++++++++++++++++ src/env/child/resolver.rs | 31 ++++++++++++++++++++++++++ src/env/mod.rs | 1 + src/env/root/mod.rs | 9 +++++++- src/lib.rs | 4 ++-- tests/host_functions.rs | 1 - 6 files changed, 88 insertions(+), 4 deletions(-) create mode 100644 src/env/child/mod.rs create mode 100644 src/env/child/resolver.rs diff --git a/src/env/child/mod.rs b/src/env/child/mod.rs new file mode 100644 index 0000000..633b17a --- /dev/null +++ b/src/env/child/mod.rs @@ -0,0 +1,46 @@ +mod resolver; + +use crate::execute::Execute; + +use self::resolver::ChildModuleImportResolver; + +use wasmi::{ + Externals, ImportsBuilder, MemoryRef, Module, ModuleInstance, RuntimeArgs, RuntimeValue, Trap, +}; + +#[derive(Debug)] +pub struct ChildRuntime<'a> { + code: &'a [u8], +} + +impl<'a> ChildRuntime<'a> { + pub fn new(code: &'a [u8]) -> Self { + Self { code } + } + + pub fn execute(&mut self) { + let module = Module::from_buffer(self.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(); + + instance + .invoke_export("main", &[], self) + .expect("Executed 'main'"); + } +} + +impl<'a> Externals for ChildRuntime<'a> { + fn invoke_index( + &mut self, + index: usize, + args: RuntimeArgs, + ) -> Result, Trap> { + match index { + _ => panic!("unknown function index"), + } + } +} diff --git a/src/env/child/resolver.rs b/src/env/child/resolver.rs new file mode 100644 index 0000000..e9578dc --- /dev/null +++ b/src/env/child/resolver.rs @@ -0,0 +1,31 @@ +use wasmi::{ + Error as InterpreterError, FuncInstance, FuncRef, ModuleImportResolver, Signature, ValueType, +}; + +//pub const EXEC_FUNC_INDEX: usize = 8; + +pub struct ChildModuleImportResolver; + +impl<'a> ModuleImportResolver for ChildModuleImportResolver { + fn resolve_func( + &self, + field_name: &str, + _signature: &Signature, + ) -> Result { + let func_ref = match field_name { + /* + "eth2_loadPreStateRoot" => FuncInstance::alloc_host( + Signature::new(&[ValueType::I32][..], None), + LOADPRESTATEROOT_FUNC_INDEX, + ), + */ + _ => { + return Err(InterpreterError::Function(format!( + "host module doesn't export function with name {}", + field_name + ))) + } + }; + Ok(func_ref) + } +} diff --git a/src/env/mod.rs b/src/env/mod.rs index dec16f3..99fea17 100644 --- a/src/env/mod.rs +++ b/src/env/mod.rs @@ -1 +1,2 @@ +pub mod child; pub mod root; diff --git a/src/env/root/mod.rs b/src/env/root/mod.rs index be02cb7..ab24888 100644 --- a/src/env/root/mod.rs +++ b/src/env/root/mod.rs @@ -3,6 +3,7 @@ mod resolver; use arrayref::array_ref; use crate::buffer::Buffer; +use crate::env::child::ChildRuntime; use crate::execute::Execute; use log::debug; @@ -187,7 +188,13 @@ impl<'a> RootRuntime<'a> { debug!("exec 0x{:x} ({} bytes)", code_ptr, code_len); - unimplemented!() + let memory = self.memory.as_ref().expect("root missing memory"); + let code = memory.get(code_ptr, code_len as usize).unwrap(); + + let mut child = ChildRuntime::new(&code); + child.execute(); + + Ok(None) } } diff --git a/src/lib.rs b/src/lib.rs index 208887a..76b925d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,6 +1,6 @@ mod buffer; -mod execute; mod env; +mod execute; -pub use execute::Execute; pub use env::root::RootRuntime; +pub use execute::Execute; diff --git a/tests/host_functions.rs b/tests/host_functions.rs index 1337930..67afd80 100644 --- a/tests/host_functions.rs +++ b/tests/host_functions.rs @@ -47,7 +47,6 @@ fn build_root(n: u8) -> [u8; 32] { } #[test] -#[should_panic] fn exec() { let child_code = nop(); From cceebd279238e9b7076d722db8d986c3e13ac283 Mon Sep 17 00:00:00 2001 From: Sam Wilson Date: Thu, 7 Nov 2019 17:01:46 -0500 Subject: [PATCH 4/7] Implement callbacks from child runtimes into the root runtime. --- Cargo.toml | 4 + src/buffer.rs | 2 +- src/env/child/mod.rs | 63 +++- src/env/child/resolver.rs | 15 +- src/env/mod.rs | 4 + src/env/root/mod.rs | 634 +++++++++++++++++++++++++++++--------- src/env/root/resolver.rs | 15 + tests/child_functions.rs | 89 ++++++ tests/host_functions.rs | 13 +- tests/utils/mod.rs | 9 + 10 files changed, 666 insertions(+), 182 deletions(-) create mode 100644 tests/child_functions.rs create mode 100644 tests/utils/mod.rs diff --git a/Cargo.toml b/Cargo.toml index 9755ea0..9d2fd9f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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" diff --git a/src/buffer.rs b/src/buffer.rs index a60774a..3bb9438 100644 --- a/src/buffer.rs +++ b/src/buffer.rs @@ -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, } diff --git a/src/env/child/mod.rs b/src/env/child/mod.rs index 633b17a..81ea45a 100644 --- a/src/env/child/mod.rs +++ b/src/env/child/mod.rs @@ -1,25 +1,26 @@ mod resolver; -use crate::execute::Execute; +use crate::env::root::{RootRuntime, StackFrame}; -use self::resolver::ChildModuleImportResolver; +use self::resolver::{externals, ChildModuleImportResolver}; + +use super::ExtResult; use wasmi::{ - Externals, ImportsBuilder, MemoryRef, Module, ModuleInstance, RuntimeArgs, RuntimeValue, Trap, + Externals, ImportsBuilder, MemoryRef, Module, ModuleInstance, ModuleRef, RuntimeArgs, + RuntimeValue, Trap, }; #[derive(Debug)] pub struct ChildRuntime<'a> { - code: &'a [u8], + instance: ModuleRef, + root: &'a RootRuntime<'a>, } impl<'a> ChildRuntime<'a> { - pub fn new(code: &'a [u8]) -> Self { - Self { code } - } + pub fn new(root: &'a RootRuntime<'a>, code: &'a [u8]) -> Self { + let module = Module::from_buffer(code).expect("Module loading to succeed"); - pub fn execute(&mut self) { - let module = Module::from_buffer(self.code).expect("Module loading to succeed"); let mut imports = ImportsBuilder::new(); imports.push_resolver("env", &ChildModuleImportResolver); @@ -27,10 +28,51 @@ impl<'a> ChildRuntime<'a> { .expect("Module instantation expected to succeed") .assert_no_start(); - instance + Self { instance, root } + } + + pub fn execute(&mut self) { + self.instance + .clone() .invoke_export("main", &[], self) .expect("Executed 'main'"); } + + 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 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())) + } } impl<'a> Externals for ChildRuntime<'a> { @@ -40,6 +82,7 @@ impl<'a> Externals for ChildRuntime<'a> { args: RuntimeArgs, ) -> Result, Trap> { match index { + externals::CALL => self.ext_call(args), _ => panic!("unknown function index"), } } diff --git a/src/env/child/resolver.rs b/src/env/child/resolver.rs index e9578dc..7ff8980 100644 --- a/src/env/child/resolver.rs +++ b/src/env/child/resolver.rs @@ -1,9 +1,11 @@ +pub mod externals { + pub const CALL: usize = 1; +} + use wasmi::{ Error as InterpreterError, FuncInstance, FuncRef, ModuleImportResolver, Signature, ValueType, }; -//pub const EXEC_FUNC_INDEX: usize = 8; - pub struct ChildModuleImportResolver; impl<'a> ModuleImportResolver for ChildModuleImportResolver { @@ -13,12 +15,11 @@ impl<'a> ModuleImportResolver for ChildModuleImportResolver { _signature: &Signature, ) -> Result { let func_ref = match field_name { - /* - "eth2_loadPreStateRoot" => FuncInstance::alloc_host( - Signature::new(&[ValueType::I32][..], None), - LOADPRESTATEROOT_FUNC_INDEX, + "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 {}", diff --git a/src/env/mod.rs b/src/env/mod.rs index 99fea17..3a9a3de 100644 --- a/src/env/mod.rs +++ b/src/env/mod.rs @@ -1,2 +1,6 @@ pub mod child; pub mod root; + +use wasmi::{RuntimeValue, Trap}; + +pub type ExtResult = Result, Trap>; diff --git a/src/env/root/mod.rs b/src/env/root/mod.rs index ab24888..b8229c5 100644 --- a/src/env/root/mod.rs +++ b/src/env/root/mod.rs @@ -9,46 +9,197 @@ use crate::execute::Execute; use log::debug; use self::resolver::{ - RuntimeModuleImportResolver, BLOCKDATACOPY_FUNC_INDEX, BLOCKDATASIZE_FUNC_INDEX, - BUFFERCLEAR_FUNC_INDEX, BUFFERGET_FUNC_INDEX, BUFFERMERGE_FUNC_INDEX, BUFFERSET_FUNC_INDEX, - EXEC_FUNC_INDEX, LOADPRESTATEROOT_FUNC_INDEX, SAVEPOSTSTATEROOT_FUNC_INDEX, + RuntimeModuleImportResolver, ARGUMENT_FUNC_INDEX, BLOCKDATACOPY_FUNC_INDEX, + BLOCKDATASIZE_FUNC_INDEX, BUFFERCLEAR_FUNC_INDEX, BUFFERGET_FUNC_INDEX, BUFFERMERGE_FUNC_INDEX, + BUFFERSET_FUNC_INDEX, EXEC_FUNC_INDEX, EXPOSE_FUNC_INDEX, LOADPRESTATEROOT_FUNC_INDEX, + RETURN_FUNC_INDEX, SAVEPOSTSTATEROOT_FUNC_INDEX, }; +use std::cell::RefCell; +use std::collections::HashSet; + +use super::ExtResult; + +use typed_builder::TypedBuilder; + use wasmi::{ - Externals, ImportsBuilder, MemoryRef, Module, ModuleInstance, RuntimeArgs, RuntimeValue, Trap, + Externals, FuncInstance, ImportsBuilder, MemoryInstance, MemoryRef, Module, ModuleInstance, + ModuleRef, RuntimeArgs, RuntimeValue, Trap, }; -pub type ExtResult = Result, Trap>; +#[derive(Debug, Clone, TypedBuilder)] +pub(crate) struct StackFrame { + memory: MemoryRef, -#[derive(Clone)] + argument_offset: u32, + argument_length: u32, + + return_offset: u32, + return_length: u32, +} + +#[derive(Debug, Clone)] pub struct RootRuntime<'a> { - pub(crate) code: &'a [u8], - pub(crate) data: &'a [u8], - pub(crate) pre_root: [u8; 32], - pub(crate) post_root: [u8; 32], - pub(crate) memory: Option, - pub(crate) buffer: Buffer, + code: &'a [u8], + data: &'a [u8], + pre_root: [u8; 32], + post_root: RefCell<[u8; 32]>, + instance: ModuleRef, + buffer: RefCell, + + call_targets: RefCell>, + call_stack: RefCell>, } impl<'a> RootRuntime<'a> { pub fn new(code: &'a [u8], data: &'a [u8], pre_root: [u8; 32]) -> RootRuntime<'a> { + let module = Module::from_buffer(code).expect("Module loading to succeed"); + + let mut imports = ImportsBuilder::new(); + imports.push_resolver("env", &RuntimeModuleImportResolver); + + let instance = ModuleInstance::new(&module, &imports) + .expect("Module instantation expected to succeed") + .assert_no_start(); + RootRuntime { + instance, code, data, pre_root, - post_root: [0u8; 32], - memory: None, - buffer: Buffer::default(), + post_root: Default::default(), + call_targets: Default::default(), + call_stack: Default::default(), + buffer: Default::default(), + } + } + + pub(crate) fn call(&self, name: &str, frame: StackFrame) -> i32 { + if !self.call_targets.borrow().contains(name) { + panic!("function `{}` is not a safe call target", name); } + + let export = self + .instance + .export_by_name(name) + .expect("Exposed name doesn't exist"); + + let func = export.as_func().expect("Exposed name isn't a function"); + + let args: &[RuntimeValue] = &[frame.argument_length.into(), frame.return_length.into()]; + + self.call_stack.borrow_mut().push(frame); + + let result = FuncInstance::invoke(&func, args, &mut self.externals()) + .expect("function provided by root runtime failed") + .expect("function provided by root runtime did not return a value") + .try_into() + .expect("funtion provided by rooot runtime return a non-i32 value"); + + self.call_stack.borrow_mut().pop().unwrap(); + + result + } + + fn externals(&self) -> RootExternals { + RootExternals(self) + } + + 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") + } + + /// 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 = std::cmp::min(src_len, top.return_length); + + MemoryInstance::transfer( + &memory, + src_ptr as usize, + &top.memory, + top.return_offset as usize, + len as usize, + ) + .unwrap(); + + Ok(Some(top.return_length.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 = std::cmp::min(dest_len, top.argument_length); + + MemoryInstance::transfer( + &top.memory, + top.argument_offset as usize, + &memory, + dest_ptr as usize, + len as usize, + ) + .unwrap(); + + Ok(Some(top.argument_length.into())) + } + + fn ext_expose(&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(); + + self.call_targets.borrow_mut().insert(name); + + Ok(None) } - fn ext_load_pre_state_root(&mut self, args: RuntimeArgs) -> ExtResult { + fn ext_load_pre_state_root(&self, args: RuntimeArgs) -> ExtResult { let ptr: u32 = args.nth(0); debug!("loadprestateroot to {}", ptr); // TODO: add checks for out of bounds access - let memory = self.memory.as_ref().expect("expects memory object"); + let memory = self.memory(); memory .set(ptr, &self.pre_root[..]) .expect("expects writing to memory to succeed"); @@ -56,26 +207,27 @@ impl<'a> RootRuntime<'a> { Ok(None) } - fn ext_save_post_state_root(&mut self, args: RuntimeArgs) -> ExtResult { + fn ext_save_post_state_root(&self, args: RuntimeArgs) -> ExtResult { let ptr: u32 = args.nth(0); debug!("savepoststateroot from {}", ptr); // TODO: add checks for out of bounds access - let memory = self.memory.as_ref().expect("expects memory object"); + let mut post_root = self.post_root.borrow_mut(); + let memory = self.memory(); memory - .get_into(ptr, &mut self.post_root[..]) + .get_into(ptr, &mut post_root[..]) .expect("expects reading from memory to succeed"); Ok(None) } - fn ext_block_data_size(&mut self, _: RuntimeArgs) -> ExtResult { + fn ext_block_data_size(&self, _: RuntimeArgs) -> ExtResult { let ret: i32 = self.data.len() as i32; debug!("blockdatasize {}", ret); Ok(Some(ret.into())) } - fn ext_block_data_copy(&mut self, args: RuntimeArgs) -> ExtResult { + fn ext_block_data_copy(&self, args: RuntimeArgs) -> ExtResult { let ptr: u32 = args.nth(0); let offset: u32 = args.nth(1); let length: u32 = args.nth(2); @@ -89,7 +241,7 @@ impl<'a> RootRuntime<'a> { let length = length as usize; // TODO: add checks for out of bounds access - let memory = self.memory.as_ref().expect("expects memory object"); + let memory = self.memory(); memory .set(ptr, &self.data[offset..length]) .expect("expects writing to memory to succeed"); @@ -97,7 +249,7 @@ impl<'a> RootRuntime<'a> { Ok(None) } - fn ext_buffer_get(&mut self, args: RuntimeArgs) -> ExtResult { + fn ext_buffer_get(&self, args: RuntimeArgs) -> ExtResult { let frame: u32 = args.nth(0); let key_ptr: u32 = args.nth(1); let value_ptr: u32 = args.nth(2); @@ -111,12 +263,12 @@ impl<'a> RootRuntime<'a> { let frame = frame as u8; // TODO: add checks for out of bounds access - let memory = self.memory.as_ref().expect("expects memory object"); + let memory = self.memory(); let key = memory.get(key_ptr, 32).expect("read to suceed"); let key = *array_ref![key, 0, 32]; - if let Some(value) = self.buffer.get(frame, key) { + if let Some(value) = self.buffer.borrow().get(frame, key) { memory .set(value_ptr, value) .expect("writing to memory to succeed"); @@ -127,7 +279,7 @@ impl<'a> RootRuntime<'a> { } } - fn ext_buffer_set(&mut self, args: RuntimeArgs) -> ExtResult { + fn ext_buffer_set(&self, args: RuntimeArgs) -> ExtResult { let frame: u32 = args.nth(0); let key_ptr: u32 = args.nth(1); let value_ptr: u32 = args.nth(2); @@ -141,7 +293,7 @@ impl<'a> RootRuntime<'a> { let frame = frame as u8; // TODO: add checks for out of bounds access - let memory = self.memory.as_ref().expect("expects memory object"); + let memory = self.memory(); let key = memory.get(key_ptr, 32).expect("read to suceed"); let key = *array_ref![key, 0, 32]; @@ -149,12 +301,12 @@ impl<'a> RootRuntime<'a> { let value = memory.get(value_ptr, 32).expect("read to suceed"); let value = *array_ref![value, 0, 32]; - self.buffer.insert(frame, key, value); + self.buffer.borrow_mut().insert(frame, key, value); Ok(None) } - fn ext_buffer_merge(&mut self, args: RuntimeArgs) -> ExtResult { + fn ext_buffer_merge(&self, args: RuntimeArgs) -> ExtResult { let frame_a: u32 = args.nth(0); let frame_b: u32 = args.nth(1); @@ -164,12 +316,12 @@ impl<'a> RootRuntime<'a> { let frame_a = frame_a as u8; let frame_b = frame_b as u8; - self.buffer.merge(frame_a, frame_b); + self.buffer.borrow_mut().merge(frame_a, frame_b); Ok(None) } - fn ext_buffer_clear(&mut self, args: RuntimeArgs) -> ExtResult { + fn ext_buffer_clear(&self, args: RuntimeArgs) -> ExtResult { let frame: u32 = args.nth(0); // TODO: add overflow check @@ -177,21 +329,21 @@ impl<'a> RootRuntime<'a> { debug!("bufferclear on frame {}", frame); - self.buffer.clear(frame); + self.buffer.borrow_mut().clear(frame); Ok(None) } - fn ext_exec(&mut self, args: RuntimeArgs) -> ExtResult { + fn ext_exec(&self, args: RuntimeArgs) -> ExtResult { let code_ptr: u32 = args.nth(0); let code_len: u32 = args.nth(1); debug!("exec 0x{:x} ({} bytes)", code_ptr, code_len); - let memory = self.memory.as_ref().expect("root missing memory"); + let memory = self.memory(); let code = memory.get(code_ptr, code_len as usize).unwrap(); - let mut child = ChildRuntime::new(&code); + let mut child = ChildRuntime::new(self, &code); child.execute(); Ok(None) @@ -200,47 +352,36 @@ impl<'a> RootRuntime<'a> { impl<'a> Execute<'a> for RootRuntime<'a> { fn execute(&'a mut self) -> [u8; 32] { - let module = Module::from_buffer(self.code).expect("Module loading to succeed"); - let mut imports = ImportsBuilder::new(); - imports.push_resolver("env", &RuntimeModuleImportResolver); - - let instance = ModuleInstance::new(&module, &imports) - .expect("Module instantation expected to succeed") - .assert_no_start(); - - self.memory = Some( - instance - .export_by_name("memory") - .expect("Module expected to have 'memory' export") - .as_memory() - .cloned() - .expect("'memory' export should be a memory"), - ); - - instance - .invoke_export("main", &[], self) + self.instance + .invoke_export("main", &[], &mut self.externals()) .expect("Executed 'main'"); - self.post_root + *self.post_root.borrow() } } -impl<'a> Externals for RootRuntime<'a> { +#[derive(Debug)] +struct RootExternals<'a, 'b>(&'a RootRuntime<'b>); + +impl<'a, 'b> Externals for RootExternals<'a, 'b> { fn invoke_index( &mut self, index: usize, args: RuntimeArgs, ) -> Result, Trap> { match index { - LOADPRESTATEROOT_FUNC_INDEX => self.ext_load_pre_state_root(args), - SAVEPOSTSTATEROOT_FUNC_INDEX => self.ext_save_post_state_root(args), - BLOCKDATASIZE_FUNC_INDEX => self.ext_block_data_size(args), - BLOCKDATACOPY_FUNC_INDEX => self.ext_block_data_copy(args), - BUFFERGET_FUNC_INDEX => self.ext_buffer_get(args), - BUFFERSET_FUNC_INDEX => self.ext_buffer_set(args), - BUFFERMERGE_FUNC_INDEX => self.ext_buffer_merge(args), - BUFFERCLEAR_FUNC_INDEX => self.ext_buffer_clear(args), - EXEC_FUNC_INDEX => self.ext_exec(args), + LOADPRESTATEROOT_FUNC_INDEX => self.0.ext_load_pre_state_root(args), + SAVEPOSTSTATEROOT_FUNC_INDEX => self.0.ext_save_post_state_root(args), + BLOCKDATASIZE_FUNC_INDEX => self.0.ext_block_data_size(args), + BLOCKDATACOPY_FUNC_INDEX => self.0.ext_block_data_copy(args), + BUFFERGET_FUNC_INDEX => self.0.ext_buffer_get(args), + BUFFERSET_FUNC_INDEX => self.0.ext_buffer_set(args), + BUFFERMERGE_FUNC_INDEX => self.0.ext_buffer_merge(args), + BUFFERCLEAR_FUNC_INDEX => self.0.ext_buffer_clear(args), + EXEC_FUNC_INDEX => self.0.ext_exec(args), + EXPOSE_FUNC_INDEX => self.0.ext_expose(args), + ARGUMENT_FUNC_INDEX => self.0.ext_argument(args), + RETURN_FUNC_INDEX => self.0.ext_return(args), _ => panic!("unknown function index"), } } @@ -250,8 +391,21 @@ impl<'a> Externals for RootRuntime<'a> { mod test { use super::*; use crate::buffer::Buffer; + use lazy_static::lazy_static; + use wabt::wat2wasm; use wasmi::memory_units::Pages; - use wasmi::{MemoryInstance, MemoryRef}; + use wasmi::MemoryInstance; + + lazy_static! { + static ref NOP: Vec = wat2wasm( + r#" + (module + (memory (export "memory") 1) + (func $main (export "main") (nop))) + "# + ) + .unwrap(); + } fn build_root(n: u8) -> [u8; 32] { let mut ret = [0u8; 32]; @@ -259,144 +413,315 @@ mod test { ret } - fn build_runtime<'a>( - data: &'a [u8], - pre_root: [u8; 32], - memory: MemoryRef, - buffer: Buffer, - ) -> RootRuntime<'a> { - RootRuntime { - code: &[], - data: data, - pre_root, - post_root: [0; 32], - memory: Some(memory), - buffer, - } + fn build_runtime<'a>(data: &'a [u8], pre_root: [u8; 32], buffer: Buffer) -> RootRuntime<'a> { + let mut rt = RootRuntime::new(&NOP, data, pre_root); + rt.buffer = buffer.into(); + rt } #[test] - fn load_pre_state_root() { + fn return_long_value_does_not_overwrite() { + let memory = MemoryInstance::alloc(Pages(1), None).unwrap(); + + let runtime = build_runtime(&[], build_root(42), Buffer::default()); + runtime.memory().set(0, &[45, 99, 7]).unwrap(); + + let frame = StackFrame::builder() + .argument_offset(0u32) + .argument_length(0u32) + .return_offset(0u32) + .return_length(2u32) + .memory(memory.clone()) + .build(); + + runtime.call_stack.borrow_mut().push(frame); + + let result: u32 = Externals::invoke_index( + &mut runtime.externals(), + RETURN_FUNC_INDEX, + [0.into(), 3.into()][..].into(), + ) + .expect("trap while calling return") + .expect("return did not return a result") + .try_into() + .expect("return did not return an integer"); + + assert_eq!(result, 2); + assert_eq!(memory.get(0, 3).unwrap(), [45, 99, 0]); + } + + #[test] + fn return_copies_value_into_parent_frame() { + let memory = MemoryInstance::alloc(Pages(1), None).unwrap(); + + let runtime = build_runtime(&[], build_root(42), Buffer::default()); + runtime.memory().set(0, &[45]).unwrap(); + + let frame = StackFrame::builder() + .argument_offset(0u32) + .argument_length(0u32) + .return_offset(0u32) + .return_length(2u32) + .memory(memory.clone()) + .build(); + + runtime.call_stack.borrow_mut().push(frame); + + let result: u32 = Externals::invoke_index( + &mut runtime.externals(), + RETURN_FUNC_INDEX, + [0.into(), 1.into()][..].into(), + ) + .expect("trap while calling return") + .expect("return did not return a result") + .try_into() + .expect("return did not return an integer"); + + assert_eq!(result, 2); + assert_eq!(memory.get(0, 1).unwrap(), [45]); + } + + #[test] + fn return_provides_buffer_size() { + let memory = MemoryInstance::alloc(Pages(1), None).unwrap(); + let runtime = build_runtime(&[], build_root(42), Buffer::default()); + + let frame = StackFrame::builder() + .argument_offset(0u32) + .argument_length(0u32) + .return_offset(0u32) + .return_length(2u32) + .memory(memory.clone()) + .build(); + + runtime.call_stack.borrow_mut().push(frame); + + let result: u32 = Externals::invoke_index( + &mut runtime.externals(), + RETURN_FUNC_INDEX, + [0.into(), 0.into()][..].into(), + ) + .expect("trap while calling return") + .expect("return did not return a result") + .try_into() + .expect("return did not return an integer"); + + assert_eq!(result, 2); + } + + #[test] + fn argument_provides_buffer_size() { + let memory = MemoryInstance::alloc(Pages(1), None).unwrap(); + let runtime = build_runtime(&[], build_root(42), Buffer::default()); + + let frame = StackFrame::builder() + .return_offset(0u32) + .return_length(0u32) + .argument_offset(0u32) + .argument_length(2u32) + .memory(memory.clone()) + .build(); + + runtime.call_stack.borrow_mut().push(frame); + + let result: u32 = Externals::invoke_index( + &mut runtime.externals(), + ARGUMENT_FUNC_INDEX, + [0.into(), 0.into()][..].into(), + ) + .expect("trap while calling argument") + .expect("argument did not return a result") + .try_into() + .expect("argument did not return an integer"); + + assert_eq!(result, 2); + } + + #[test] + fn argument_copies_value_from_parent_frame() { + let memory = MemoryInstance::alloc(Pages(1), None).unwrap(); + memory.set(0, &[32, 123]).unwrap(); + + let runtime = build_runtime(&[], build_root(42), Buffer::default()); + runtime.memory().set(0, &[32, 45]).unwrap(); + + let frame = StackFrame::builder() + .argument_offset(0u32) + .argument_length(2u32) + .return_offset(0u32) + .return_length(0u32) + .memory(memory.clone()) + .build(); + + runtime.call_stack.borrow_mut().push(frame); + + let result: u32 = Externals::invoke_index( + &mut runtime.externals(), + ARGUMENT_FUNC_INDEX, + [0.into(), 1.into()][..].into(), + ) + .expect("trap while calling return") + .expect("return did not return a result") + .try_into() + .expect("return did not return an integer"); + + assert_eq!(result, 2); + assert_eq!(runtime.memory().get(0, 2).unwrap(), [32, 45]); + } + + #[test] + fn argument_long_value_does_not_leak() { let memory = MemoryInstance::alloc(Pages(1), None).unwrap(); - let mut runtime = build_runtime(&[], build_root(42), memory, Buffer::default()); + memory.set(0, &[32, 123, 234]).unwrap(); + + let runtime = build_runtime(&[], build_root(42), Buffer::default()); + runtime.memory().set(0, &[45, 45, 45]).unwrap(); + + let frame = StackFrame::builder() + .argument_offset(0u32) + .argument_length(2u32) + .return_offset(0u32) + .return_length(0u32) + .memory(memory.clone()) + .build(); - assert!(Externals::invoke_index( - &mut runtime, + runtime.call_stack.borrow_mut().push(frame); + + let result: u32 = Externals::invoke_index( + &mut runtime.externals(), + ARGUMENT_FUNC_INDEX, + [0.into(), 3.into()][..].into(), + ) + .expect("trap while calling return") + .expect("return did not return a result") + .try_into() + .expect("return did not return an integer"); + + assert_eq!(result, 2); + assert_eq!(runtime.memory().get(0, 3).unwrap(), [32, 123, 45]); + } + + #[test] + fn load_pre_state_root() { + let runtime = build_runtime(&[], build_root(42), Buffer::default()); + + Externals::invoke_index( + &mut runtime.externals(), LOADPRESTATEROOT_FUNC_INDEX, - [0.into()][..].into() + [0.into()][..].into(), ) - .is_ok()); + .unwrap(); - assert_eq!(runtime.memory.unwrap().get(0, 32).unwrap(), build_root(42)); + assert_eq!(runtime.memory().get(0, 32).unwrap(), build_root(42)); } #[test] fn save_post_state_root() { - let memory = MemoryInstance::alloc(Pages(1), None).unwrap(); - memory.set(100, &build_root(42)).expect("sets memory"); + let runtime = build_runtime(&[], build_root(0), Buffer::default()); - let mut runtime = build_runtime(&[], build_root(0), memory, Buffer::default()); + let memory = runtime.memory(); + memory.set(100, &build_root(42)).expect("sets memory"); - assert!(Externals::invoke_index( - &mut runtime, + Externals::invoke_index( + &mut runtime.externals(), SAVEPOSTSTATEROOT_FUNC_INDEX, - [100.into()][..].into() + [100.into()][..].into(), ) - .is_ok()); + .unwrap(); - assert_eq!( - runtime.memory.unwrap().get(100, 32).unwrap(), - build_root(42) - ); + assert_eq!(runtime.memory().get(100, 32).unwrap(), build_root(42)); } #[test] fn block_data_size() { - let memory = MemoryInstance::alloc(Pages(1), None).unwrap(); - let mut runtime = build_runtime(&[1; 42], build_root(0), memory, Buffer::default()); + let runtime = build_runtime(&[1; 42], build_root(0), Buffer::default()); assert_eq!( - Externals::invoke_index(&mut runtime, BLOCKDATASIZE_FUNC_INDEX, [][..].into()) - .unwrap() - .unwrap(), + Externals::invoke_index( + &mut runtime.externals(), + BLOCKDATASIZE_FUNC_INDEX, + [][..].into() + ) + .unwrap() + .unwrap(), 42.into() ); } #[test] fn block_data_copy() { - let memory = MemoryInstance::alloc(Pages(1), None).unwrap(); let data: Vec = (1..21).collect(); - let mut runtime = build_runtime(&data, build_root(0), memory, Buffer::default()); + let runtime = build_runtime(&data, build_root(0), Buffer::default()); - assert!(Externals::invoke_index( - &mut runtime, + Externals::invoke_index( + &mut runtime.externals(), BLOCKDATACOPY_FUNC_INDEX, - [1.into(), 0.into(), 20.into()][..].into() + [1.into(), 0.into(), 20.into()][..].into(), ) - .is_ok()); + .unwrap(); - assert!(Externals::invoke_index( - &mut runtime, + Externals::invoke_index( + &mut runtime.externals(), BLOCKDATACOPY_FUNC_INDEX, - [23.into(), 10.into(), 20.into()][..].into() + [23.into(), 10.into(), 20.into()][..].into(), ) - .is_ok()); + .unwrap(); // This checks that the entire data blob was loaded into memory. - assert_eq!(runtime.clone().memory.unwrap().get(1, 20).unwrap(), data); + assert_eq!(runtime.clone().memory().get(1, 20).unwrap(), data); // This checks that the data after the offset was loaded into memory. - assert_eq!(runtime.memory.unwrap().get(23, 10).unwrap()[..], data[10..]); + assert_eq!(runtime.memory().get(23, 10).unwrap()[..], data[10..]); } #[test] fn buffer_get() { - let memory = MemoryInstance::alloc(Pages(1), None).unwrap(); let mut buffer = Buffer::default(); - // Save the 32 byte key at position 0 in memory - memory.set(0, &[1u8; 32]).unwrap(); - // Insert a value into the buffer that corresponds to the above key. buffer.insert(0, [1u8; 32], build_root(42)); - let mut runtime = build_runtime(&[], build_root(0), memory, buffer); + let runtime = build_runtime(&[], build_root(0), buffer); + + let memory = runtime.memory(); + + // Save the 32 byte key at position 0 in memory + memory.set(0, &[1u8; 32]).unwrap(); - assert!(Externals::invoke_index( - &mut runtime, + Externals::invoke_index( + &mut runtime.externals(), BUFFERGET_FUNC_INDEX, - [0.into(), 0.into(), 32.into()][..].into() + [0.into(), 0.into(), 32.into()][..].into(), ) - .is_ok()); + .unwrap(); assert_eq!( - runtime.clone().memory.unwrap().get(32, 32).unwrap(), + runtime.clone().memory().get(32, 32).unwrap(), build_root(42) ); } #[test] fn buffer_set() { - let memory = MemoryInstance::alloc(Pages(1), None).unwrap(); + let runtime = build_runtime(&[], build_root(0), Buffer::default()); + + let memory = runtime.memory(); memory.set(0, &[1u8; 32]).unwrap(); memory.set(32, &[2u8; 32]).unwrap(); - let mut runtime = build_runtime(&[], build_root(0), memory, Buffer::default()); - - assert!(Externals::invoke_index( - &mut runtime, + Externals::invoke_index( + &mut runtime.externals(), BUFFERSET_FUNC_INDEX, - [0.into(), 0.into(), 32.into()][..].into() + [0.into(), 0.into(), 32.into()][..].into(), ) - .is_ok()); + .unwrap(); - assert_eq!(runtime.buffer.get(0, [1u8; 32]), Some(&[2u8; 32])); + let buffer = runtime.buffer.borrow(); + assert_eq!(buffer.get(0, [1u8; 32]), Some(&[2u8; 32])); } #[test] fn buffer_merge() { - let memory = MemoryInstance::alloc(Pages(1), None).unwrap(); let mut buffer = Buffer::default(); buffer.insert(1, [0u8; 32], [0u8; 32]); @@ -404,40 +729,41 @@ mod test { buffer.insert(2, [2u8; 32], [2u8; 32]); buffer.insert(2, [0u8; 32], [3u8; 32]); - let mut runtime = build_runtime(&[], build_root(0), memory, buffer); + let runtime = build_runtime(&[], build_root(0), buffer); - assert!(Externals::invoke_index( - &mut runtime, + Externals::invoke_index( + &mut runtime.externals(), BUFFERMERGE_FUNC_INDEX, - [1.into(), 2.into()][..].into() + [1.into(), 2.into()][..].into(), ) - .is_ok()); - - assert_eq!(runtime.buffer.get(1, [0u8; 32]), Some(&[3u8; 32])); - assert_eq!(runtime.buffer.get(1, [1u8; 32]), Some(&[1u8; 32])); - assert_eq!(runtime.buffer.get(1, [2u8; 32]), Some(&[2u8; 32])); - assert_eq!(runtime.buffer.get(2, [0u8; 32]), Some(&[3u8; 32])); - assert_eq!(runtime.buffer.get(2, [2u8; 32]), Some(&[2u8; 32])); + .unwrap(); + + let buffer = runtime.buffer.borrow(); + assert_eq!(buffer.get(1, [0u8; 32]), Some(&[3u8; 32])); + assert_eq!(buffer.get(1, [1u8; 32]), Some(&[1u8; 32])); + assert_eq!(buffer.get(1, [2u8; 32]), Some(&[2u8; 32])); + assert_eq!(buffer.get(2, [0u8; 32]), Some(&[3u8; 32])); + assert_eq!(buffer.get(2, [2u8; 32]), Some(&[2u8; 32])); } #[test] fn buffer_clear() { - let memory = MemoryInstance::alloc(Pages(1), None).unwrap(); let mut buffer = Buffer::default(); buffer.insert(1, [0u8; 32], [0u8; 32]); buffer.insert(2, [0u8; 32], [0u8; 32]); - let mut runtime = build_runtime(&[], build_root(0), memory, buffer); + let runtime = build_runtime(&[], build_root(0), buffer); - assert!(Externals::invoke_index( - &mut runtime, + Externals::invoke_index( + &mut runtime.externals(), BUFFERCLEAR_FUNC_INDEX, - [2.into()][..].into() + [2.into()][..].into(), ) - .is_ok()); + .unwrap(); - assert_eq!(runtime.buffer.get(1, [0u8; 32]), Some(&[0u8; 32])); - assert_eq!(runtime.buffer.get(2, [0u8; 32]), None); + let buffer = runtime.buffer.borrow(); + assert_eq!(buffer.get(1, [0u8; 32]), Some(&[0u8; 32])); + assert_eq!(buffer.get(2, [0u8; 32]), None); } } diff --git a/src/env/root/resolver.rs b/src/env/root/resolver.rs index dc5a089..40f554b 100644 --- a/src/env/root/resolver.rs +++ b/src/env/root/resolver.rs @@ -11,6 +11,9 @@ pub const BUFFERSET_FUNC_INDEX: usize = 5; pub const BUFFERMERGE_FUNC_INDEX: usize = 6; pub const BUFFERCLEAR_FUNC_INDEX: usize = 7; pub const EXEC_FUNC_INDEX: usize = 8; +pub const EXPOSE_FUNC_INDEX: usize = 9; +pub const ARGUMENT_FUNC_INDEX: usize = 10; +pub const RETURN_FUNC_INDEX: usize = 11; pub struct RuntimeModuleImportResolver; @@ -60,6 +63,18 @@ impl<'a> ModuleImportResolver for RuntimeModuleImportResolver { Signature::new(&[ValueType::I32, ValueType::I32][..], None), EXEC_FUNC_INDEX, ), + "eth2_expose" => FuncInstance::alloc_host( + Signature::new(&[ValueType::I32, ValueType::I32][..], None), + EXPOSE_FUNC_INDEX, + ), + "eth2_argument" => FuncInstance::alloc_host( + Signature::new(&[ValueType::I32, ValueType::I32][..], Some(ValueType::I32)), + ARGUMENT_FUNC_INDEX, + ), + "eth2_return" => FuncInstance::alloc_host( + Signature::new(&[ValueType::I32, ValueType::I32][..], Some(ValueType::I32)), + RETURN_FUNC_INDEX, + ), _ => { return Err(InterpreterError::Function(format!( "host module doesn't export function with name {}", diff --git a/tests/child_functions.rs b/tests/child_functions.rs new file mode 100644 index 0000000..dd84441 --- /dev/null +++ b/tests/child_functions.rs @@ -0,0 +1,89 @@ +mod utils; + +use ewasm::{Execute, RootRuntime}; +use utils::escape; +use wabt::wat2wasm; + +fn compile_wat(child_code: &str) -> Vec { + let child_asm = wat2wasm(child_code).unwrap(); + + wat2wasm(format!( + r#" + (module + (import "env" "eth2_exec" (func $exec (param i32) (param i32))) + (import "env" "eth2_expose" (func $expose (param i32) (param i32))) + (import "env" "eth2_return" (func $return (param i32) (param i32) (result i32))) + (import "env" "eth2_argument" (func $argument (param i32) (param i32) (result i32))) + + (memory (export "memory") 1) + (data (i32.const 0) "some_func") + (data (i32.const 10) "{}") + (func $some_func + (export "some_func") + (param i32) + (param i32) + (result i32) + + (; Read the argument, and check the result ;) + (drop (call $argument (i32.const 89) (i32.const 4))) + (if (i32.ne (i32.load (i32.const 89)) (i32.const 9999)) + (then (unreachable))) + + (; Return a value to the caller ;) + (i32.store (i32.const 99) (i32.const 8888)) + (drop (call $return (i32.const 99) (i32.const 4))) + + (i32.const 6654)) + + (func $main (export "main") + (call $expose (i32.const 0) (i32.const 9)) + (call $exec (i32.const 10) (i32.const {}))) + )"#, + escape(&child_asm), + child_asm.len(), + )) + .unwrap() +} + +#[test] +fn call() { + let child_code = r#" + (module + (import + "env" + "eth2_call" + (func + $eth2_call + (param i32) + (param i32) + (param i32) + (param i32) + (param i32) + (param i32) + (result i32))) + (memory (export "memory") 1) + (data (i32.const 0) "some_func") + (func $main (export "main") (local $x i32) + (i32.store (i32.const 10) (i32.const 9999)) + (set_local $x + (call + $eth2_call + (i32.const 0) + (i32.const 9) + (i32.const 10) + (i32.const 4) + (i32.const 15) + (i32.const 4))) + (if + (i32.ne (get_local $x) (i32.const 6654)) + (then (unreachable))) + (if + (i32.ne (i32.load (i32.const 15)) (i32.const 8888)) + (then (unreachable))))) + "#; + + let code = compile_wat(child_code); + + let mut runtime = RootRuntime::new(&code, &[], [0u8; 32]); + runtime.execute(); +} diff --git a/tests/host_functions.rs b/tests/host_functions.rs index 67afd80..f4c96b2 100644 --- a/tests/host_functions.rs +++ b/tests/host_functions.rs @@ -1,20 +1,13 @@ +mod utils; + use ewasm::{Execute, RootRuntime}; +use utils::escape; use wabt::wat2wasm; fn nop() -> Vec { wat2wasm(r#"(module (func $main (export "main") (nop)))"#).unwrap() } -fn escape(bytes: &[u8]) -> String { - let mut output = String::with_capacity(bytes.len() * 4); - - for byte in bytes { - output.push_str(&format!(r#"\{:02x}"#, byte)); - } - - output -} - fn compile_wat(code: &str) -> Vec { wat2wasm( [ diff --git a/tests/utils/mod.rs b/tests/utils/mod.rs new file mode 100644 index 0000000..d838a89 --- /dev/null +++ b/tests/utils/mod.rs @@ -0,0 +1,9 @@ +pub fn escape(bytes: &[u8]) -> String { + let mut output = String::with_capacity(bytes.len() * 4); + + for byte in bytes { + output.push_str(&format!(r#"\{:02x}"#, byte)); + } + + output +} From ba3accbe1736ee09a75f43f14f5dc2836ba52cc4 Mon Sep 17 00:00:00 2001 From: Sam Wilson Date: Fri, 8 Nov 2019 14:37:42 -0500 Subject: [PATCH 5/7] Use Rc/Weak to link child runtimes to roots In the next commit I'm planning to associate child runtimes with an identifier, to permit calls from the EE back into smart contracts. That requires RootRuntime to own a Vec (or similar.) Using a reference would make that impossible. --- src/env/child/mod.rs | 14 ++++-- src/env/root/mod.rs | 100 +++++++++++++++++++++++++------------------ 2 files changed, 69 insertions(+), 45 deletions(-) diff --git a/src/env/child/mod.rs b/src/env/child/mod.rs index 81ea45a..d7c8c7c 100644 --- a/src/env/child/mod.rs +++ b/src/env/child/mod.rs @@ -1,6 +1,6 @@ mod resolver; -use crate::env::root::{RootRuntime, StackFrame}; +use crate::env::root::{RootRuntime, RootRuntimeWeak, StackFrame}; use self::resolver::{externals, ChildModuleImportResolver}; @@ -14,11 +14,11 @@ use wasmi::{ #[derive(Debug)] pub struct ChildRuntime<'a> { instance: ModuleRef, - root: &'a RootRuntime<'a>, + root: RootRuntimeWeak<'a>, } impl<'a> ChildRuntime<'a> { - pub fn new(root: &'a RootRuntime<'a>, code: &'a [u8]) -> Self { + 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(); @@ -47,6 +47,12 @@ impl<'a> ChildRuntime<'a> { .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(); @@ -69,7 +75,7 @@ impl<'a> ChildRuntime<'a> { .memory(memory) .build(); - let retcode = self.root.call(&name, frame); + let retcode = self.root().call(&name, frame); Ok(Some(retcode.into())) } diff --git a/src/env/root/mod.rs b/src/env/root/mod.rs index b8229c5..84352f7 100644 --- a/src/env/root/mod.rs +++ b/src/env/root/mod.rs @@ -17,6 +17,7 @@ use self::resolver::{ use std::cell::RefCell; use std::collections::HashSet; +use std::rc::{Rc, Weak}; use super::ExtResult; @@ -39,20 +40,19 @@ pub(crate) struct StackFrame { } #[derive(Debug, Clone)] -pub struct RootRuntime<'a> { - code: &'a [u8], - data: &'a [u8], - pre_root: [u8; 32], - post_root: RefCell<[u8; 32]>, - instance: ModuleRef, - buffer: RefCell, +pub(crate) struct RootRuntimeWeak<'a>(Weak>); - call_targets: RefCell>, - call_stack: RefCell>, +impl<'a> RootRuntimeWeak<'a> { + pub fn upgrade(&self) -> Option> { + self.0.upgrade().map(RootRuntime) + } } +#[derive(Debug, Clone)] +pub struct RootRuntime<'a>(Rc>); + impl<'a> RootRuntime<'a> { - pub fn new(code: &'a [u8], data: &'a [u8], pre_root: [u8; 32]) -> RootRuntime<'a> { + pub fn new<'b>(code: &'b [u8], data: &'a [u8], pre_root: [u8; 32]) -> RootRuntime<'a> { let module = Module::from_buffer(code).expect("Module loading to succeed"); let mut imports = ImportsBuilder::new(); @@ -62,24 +62,24 @@ impl<'a> RootRuntime<'a> { .expect("Module instantation expected to succeed") .assert_no_start(); - RootRuntime { + RootRuntime(Rc::new(Inner { instance, - code, data, pre_root, post_root: Default::default(), call_targets: Default::default(), call_stack: Default::default(), buffer: Default::default(), - } + })) } pub(crate) fn call(&self, name: &str, frame: StackFrame) -> i32 { - if !self.call_targets.borrow().contains(name) { + if !self.0.call_targets.borrow().contains(name) { panic!("function `{}` is not a safe call target", name); } let export = self + .0 .instance .export_by_name(name) .expect("Exposed name doesn't exist"); @@ -88,7 +88,7 @@ impl<'a> RootRuntime<'a> { let args: &[RuntimeValue] = &[frame.argument_length.into(), frame.return_length.into()]; - self.call_stack.borrow_mut().push(frame); + self.0.call_stack.borrow_mut().push(frame); let result = FuncInstance::invoke(&func, args, &mut self.externals()) .expect("function provided by root runtime failed") @@ -96,7 +96,7 @@ impl<'a> RootRuntime<'a> { .try_into() .expect("funtion provided by rooot runtime return a non-i32 value"); - self.call_stack.borrow_mut().pop().unwrap(); + self.0.call_stack.borrow_mut().pop().unwrap(); result } @@ -106,7 +106,8 @@ impl<'a> RootRuntime<'a> { } fn memory(&self) -> MemoryRef { - self.instance + self.0 + .instance .export_by_name("memory") .expect("Module expected to have 'memory' export") .as_memory() @@ -114,6 +115,10 @@ impl<'a> RootRuntime<'a> { .expect("'memory' export should be a memory") } + pub(crate) fn downgrade(&self) -> RootRuntimeWeak<'a> { + RootRuntimeWeak(Rc::downgrade(&self.0)) + } + /// Copies data from the given offset and length into the buffer allocated /// by the caller. Returns the total size of the caller's buffer. /// @@ -128,7 +133,7 @@ impl<'a> RootRuntime<'a> { let src_ptr: u32 = args.nth(0); let src_len: u32 = args.nth(1); - let call_stack = self.call_stack.borrow(); + let call_stack = self.0.call_stack.borrow(); let top = call_stack .last() .expect("eth2_return requires a call stack"); @@ -161,7 +166,7 @@ impl<'a> RootRuntime<'a> { let dest_ptr: u32 = args.nth(0); let dest_len: u32 = args.nth(1); - let call_stack = self.call_stack.borrow(); + let call_stack = self.0.call_stack.borrow(); let top = call_stack .last() .expect("eth2_argument requires a call stack"); @@ -188,7 +193,7 @@ impl<'a> RootRuntime<'a> { let name_bytes = memory.get(name_ptr, name_len as usize).unwrap(); let name = String::from_utf8(name_bytes).unwrap(); - self.call_targets.borrow_mut().insert(name); + self.0.call_targets.borrow_mut().insert(name); Ok(None) } @@ -201,7 +206,7 @@ impl<'a> RootRuntime<'a> { // TODO: add checks for out of bounds access let memory = self.memory(); memory - .set(ptr, &self.pre_root[..]) + .set(ptr, &self.0.pre_root[..]) .expect("expects writing to memory to succeed"); Ok(None) @@ -212,7 +217,7 @@ impl<'a> RootRuntime<'a> { debug!("savepoststateroot from {}", ptr); // TODO: add checks for out of bounds access - let mut post_root = self.post_root.borrow_mut(); + let mut post_root = self.0.post_root.borrow_mut(); let memory = self.memory(); memory .get_into(ptr, &mut post_root[..]) @@ -222,7 +227,7 @@ impl<'a> RootRuntime<'a> { } fn ext_block_data_size(&self, _: RuntimeArgs) -> ExtResult { - let ret: i32 = self.data.len() as i32; + let ret: i32 = self.0.data.len() as i32; debug!("blockdatasize {}", ret); Ok(Some(ret.into())) } @@ -243,7 +248,7 @@ impl<'a> RootRuntime<'a> { // TODO: add checks for out of bounds access let memory = self.memory(); memory - .set(ptr, &self.data[offset..length]) + .set(ptr, &self.0.data[offset..length]) .expect("expects writing to memory to succeed"); Ok(None) @@ -268,7 +273,7 @@ impl<'a> RootRuntime<'a> { let key = memory.get(key_ptr, 32).expect("read to suceed"); let key = *array_ref![key, 0, 32]; - if let Some(value) = self.buffer.borrow().get(frame, key) { + if let Some(value) = self.0.buffer.borrow().get(frame, key) { memory .set(value_ptr, value) .expect("writing to memory to succeed"); @@ -301,7 +306,7 @@ impl<'a> RootRuntime<'a> { let value = memory.get(value_ptr, 32).expect("read to suceed"); let value = *array_ref![value, 0, 32]; - self.buffer.borrow_mut().insert(frame, key, value); + self.0.buffer.borrow_mut().insert(frame, key, value); Ok(None) } @@ -316,7 +321,7 @@ impl<'a> RootRuntime<'a> { let frame_a = frame_a as u8; let frame_b = frame_b as u8; - self.buffer.borrow_mut().merge(frame_a, frame_b); + self.0.buffer.borrow_mut().merge(frame_a, frame_b); Ok(None) } @@ -329,7 +334,7 @@ impl<'a> RootRuntime<'a> { debug!("bufferclear on frame {}", frame); - self.buffer.borrow_mut().clear(frame); + self.0.buffer.borrow_mut().clear(frame); Ok(None) } @@ -343,20 +348,33 @@ impl<'a> RootRuntime<'a> { let memory = self.memory(); let code = memory.get(code_ptr, code_len as usize).unwrap(); - let mut child = ChildRuntime::new(self, &code); + let mut child = ChildRuntime::new(self.downgrade(), &code); child.execute(); Ok(None) } } +#[derive(Debug)] +struct Inner<'a> { + data: &'a [u8], + pre_root: [u8; 32], + post_root: RefCell<[u8; 32]>, + instance: ModuleRef, + buffer: RefCell, + + call_targets: RefCell>, + call_stack: RefCell>, +} + impl<'a> Execute<'a> for RootRuntime<'a> { fn execute(&'a mut self) -> [u8; 32] { - self.instance + self.0 + .instance .invoke_export("main", &[], &mut self.externals()) .expect("Executed 'main'"); - *self.post_root.borrow() + *self.0.post_root.borrow() } } @@ -415,7 +433,7 @@ mod test { fn build_runtime<'a>(data: &'a [u8], pre_root: [u8; 32], buffer: Buffer) -> RootRuntime<'a> { let mut rt = RootRuntime::new(&NOP, data, pre_root); - rt.buffer = buffer.into(); + Rc::get_mut(&mut rt.0).unwrap().buffer = buffer.into(); rt } @@ -434,7 +452,7 @@ mod test { .memory(memory.clone()) .build(); - runtime.call_stack.borrow_mut().push(frame); + runtime.0.call_stack.borrow_mut().push(frame); let result: u32 = Externals::invoke_index( &mut runtime.externals(), @@ -465,7 +483,7 @@ mod test { .memory(memory.clone()) .build(); - runtime.call_stack.borrow_mut().push(frame); + runtime.0.call_stack.borrow_mut().push(frame); let result: u32 = Externals::invoke_index( &mut runtime.externals(), @@ -494,7 +512,7 @@ mod test { .memory(memory.clone()) .build(); - runtime.call_stack.borrow_mut().push(frame); + runtime.0.call_stack.borrow_mut().push(frame); let result: u32 = Externals::invoke_index( &mut runtime.externals(), @@ -522,7 +540,7 @@ mod test { .memory(memory.clone()) .build(); - runtime.call_stack.borrow_mut().push(frame); + runtime.0.call_stack.borrow_mut().push(frame); let result: u32 = Externals::invoke_index( &mut runtime.externals(), @@ -553,7 +571,7 @@ mod test { .memory(memory.clone()) .build(); - runtime.call_stack.borrow_mut().push(frame); + runtime.0.call_stack.borrow_mut().push(frame); let result: u32 = Externals::invoke_index( &mut runtime.externals(), @@ -585,7 +603,7 @@ mod test { .memory(memory.clone()) .build(); - runtime.call_stack.borrow_mut().push(frame); + runtime.0.call_stack.borrow_mut().push(frame); let result: u32 = Externals::invoke_index( &mut runtime.externals(), @@ -716,7 +734,7 @@ mod test { ) .unwrap(); - let buffer = runtime.buffer.borrow(); + let buffer = runtime.0.buffer.borrow(); assert_eq!(buffer.get(0, [1u8; 32]), Some(&[2u8; 32])); } @@ -738,7 +756,7 @@ mod test { ) .unwrap(); - let buffer = runtime.buffer.borrow(); + let buffer = runtime.0.buffer.borrow(); assert_eq!(buffer.get(1, [0u8; 32]), Some(&[3u8; 32])); assert_eq!(buffer.get(1, [1u8; 32]), Some(&[1u8; 32])); assert_eq!(buffer.get(1, [2u8; 32]), Some(&[2u8; 32])); @@ -762,7 +780,7 @@ mod test { ) .unwrap(); - let buffer = runtime.buffer.borrow(); + let buffer = runtime.0.buffer.borrow(); assert_eq!(buffer.get(1, [0u8; 32]), Some(&[0u8; 32])); assert_eq!(buffer.get(2, [0u8; 32]), None); } From 8a1fbb02ffc769aeb72ee54181f62544b822a000 Mon Sep 17 00:00:00 2001 From: Sam Wilson Date: Fri, 8 Nov 2019 18:11:59 -0500 Subject: [PATCH 6/7] Split eth2_exec into eth2_loadModule and eth2_callModule Root runtimes can now load a Wasm module into a numbered "slot", instead of having to load and execute them at the same time as before. This arrangement permits calls from the root runtime back into the child runtimes. --- src/env/child/mod.rs | 50 ++++++++--- src/env/mod.rs | 15 +++- src/env/root/mod.rs | 179 +++++++++++++++++++++++++++------------ src/env/root/resolver.rs | 13 ++- src/execute.rs | 4 +- tests/child_functions.rs | 85 +++++++++++++------ tests/host_functions.rs | 51 ++++++----- 7 files changed, 276 insertions(+), 121 deletions(-) diff --git a/src/env/child/mod.rs b/src/env/child/mod.rs index d7c8c7c..bd1f0ac 100644 --- a/src/env/child/mod.rs +++ b/src/env/child/mod.rs @@ -1,20 +1,24 @@ mod resolver; -use crate::env::root::{RootRuntime, RootRuntimeWeak, StackFrame}; +use crate::env::root::{RootRuntime, RootRuntimeWeak}; use self::resolver::{externals, ChildModuleImportResolver}; -use super::ExtResult; +use std::cell::RefCell; + +use super::{ExtResult, StackFrame}; use wasmi::{ - Externals, ImportsBuilder, MemoryRef, Module, ModuleInstance, ModuleRef, RuntimeArgs, - RuntimeValue, Trap, + Externals, FuncInstance, ImportsBuilder, MemoryRef, Module, ModuleInstance, ModuleRef, + RuntimeArgs, RuntimeValue, Trap, }; #[derive(Debug)] pub struct ChildRuntime<'a> { instance: ModuleRef, root: RootRuntimeWeak<'a>, + + call_stack: RefCell>, } impl<'a> ChildRuntime<'a> { @@ -28,14 +32,33 @@ impl<'a> ChildRuntime<'a> { .expect("Module instantation expected to succeed") .assert_no_start(); - Self { instance, root } + Self { + instance, + root, + call_stack: Default::default(), + } } - pub fn execute(&mut self) { - self.instance - .clone() - .invoke_export("main", &[], self) - .expect("Executed 'main'"); + 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 { @@ -81,14 +104,17 @@ impl<'a> ChildRuntime<'a> { } } -impl<'a> Externals for ChildRuntime<'a> { +#[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, Trap> { match index { - externals::CALL => self.ext_call(args), + externals::CALL => self.0.ext_call(args), _ => panic!("unknown function index"), } } diff --git a/src/env/mod.rs b/src/env/mod.rs index 3a9a3de..1b45ce8 100644 --- a/src/env/mod.rs +++ b/src/env/mod.rs @@ -1,6 +1,19 @@ pub mod child; pub mod root; -use wasmi::{RuntimeValue, Trap}; +use typed_builder::TypedBuilder; + +use wasmi::{MemoryRef, RuntimeValue, Trap}; pub type ExtResult = Result, Trap>; + +#[derive(Debug, Clone, TypedBuilder)] +struct StackFrame { + memory: MemoryRef, + + argument_offset: u32, + argument_length: u32, + + return_offset: u32, + return_length: u32, +} diff --git a/src/env/root/mod.rs b/src/env/root/mod.rs index 84352f7..17ba2d5 100644 --- a/src/env/root/mod.rs +++ b/src/env/root/mod.rs @@ -11,34 +11,22 @@ use log::debug; use self::resolver::{ RuntimeModuleImportResolver, ARGUMENT_FUNC_INDEX, BLOCKDATACOPY_FUNC_INDEX, BLOCKDATASIZE_FUNC_INDEX, BUFFERCLEAR_FUNC_INDEX, BUFFERGET_FUNC_INDEX, BUFFERMERGE_FUNC_INDEX, - BUFFERSET_FUNC_INDEX, EXEC_FUNC_INDEX, EXPOSE_FUNC_INDEX, LOADPRESTATEROOT_FUNC_INDEX, - RETURN_FUNC_INDEX, SAVEPOSTSTATEROOT_FUNC_INDEX, + BUFFERSET_FUNC_INDEX, CALLMODULE_FUNC_INDEX, EXPOSE_FUNC_INDEX, LOADMODULE_FUNC_INDEX, + LOADPRESTATEROOT_FUNC_INDEX, RETURN_FUNC_INDEX, SAVEPOSTSTATEROOT_FUNC_INDEX, }; use std::cell::RefCell; -use std::collections::HashSet; +use std::collections::hash_map::Entry; +use std::collections::{HashMap, HashSet}; use std::rc::{Rc, Weak}; -use super::ExtResult; - -use typed_builder::TypedBuilder; +use super::{ExtResult, StackFrame}; use wasmi::{ Externals, FuncInstance, ImportsBuilder, MemoryInstance, MemoryRef, Module, ModuleInstance, ModuleRef, RuntimeArgs, RuntimeValue, Trap, }; -#[derive(Debug, Clone, TypedBuilder)] -pub(crate) struct StackFrame { - memory: MemoryRef, - - argument_offset: u32, - argument_length: u32, - - return_offset: u32, - return_length: u32, -} - #[derive(Debug, Clone)] pub(crate) struct RootRuntimeWeak<'a>(Weak>); @@ -66,6 +54,7 @@ impl<'a> RootRuntime<'a> { instance, data, pre_root, + children: Default::default(), post_root: Default::default(), call_targets: Default::default(), call_stack: Default::default(), @@ -73,7 +62,7 @@ impl<'a> RootRuntime<'a> { })) } - pub(crate) fn call(&self, name: &str, frame: StackFrame) -> i32 { + pub(super) fn call(&self, name: &str, frame: StackFrame) -> i32 { if !self.0.call_targets.borrow().contains(name) { panic!("function `{}` is not a safe call target", name); } @@ -86,11 +75,11 @@ impl<'a> RootRuntime<'a> { let func = export.as_func().expect("Exposed name isn't a function"); - let args: &[RuntimeValue] = &[frame.argument_length.into(), frame.return_length.into()]; - self.0.call_stack.borrow_mut().push(frame); - let result = FuncInstance::invoke(&func, args, &mut self.externals()) + let mut externals = RootExternals(self); + + let result = FuncInstance::invoke(&func, &[], &mut externals) .expect("function provided by root runtime failed") .expect("function provided by root runtime did not return a value") .try_into() @@ -101,10 +90,6 @@ impl<'a> RootRuntime<'a> { result } - fn externals(&self) -> RootExternals { - RootExternals(self) - } - fn memory(&self) -> MemoryRef { self.0 .instance @@ -339,20 +324,86 @@ impl<'a> RootRuntime<'a> { Ok(None) } - fn ext_exec(&self, args: RuntimeArgs) -> ExtResult { - let code_ptr: u32 = args.nth(0); - let code_len: u32 = args.nth(1); + /// Loads a compiled Wasm module from memory into the slot specified. + /// + /// # Signature + /// + /// ```text + /// eth2_loadModule(slot: u32, code_offset: u32, code_length: u32) -> () + /// ``` + fn ext_load_module(&self, args: RuntimeArgs) -> ExtResult { + let slot: u32 = args.nth(0); + let code_ptr: u32 = args.nth(1); + let code_len: u32 = args.nth(2); + + debug!( + "load module 0x{:x} ({} bytes) into {}", + code_ptr, code_len, slot + ); + + let mut children = self.0.children.borrow_mut(); - debug!("exec 0x{:x} ({} bytes)", code_ptr, code_len); + let entry = match children.entry(slot) { + Entry::Occupied(_) => panic!("reusing module slot identifiers not supported"), + Entry::Vacant(x) => x, + }; let memory = self.memory(); let code = memory.get(code_ptr, code_len as usize).unwrap(); - let mut child = ChildRuntime::new(self.downgrade(), &code); - child.execute(); + let child = ChildRuntime::new(self.downgrade(), &code); + entry.insert(child); Ok(None) } + + /// Calls the function `name` from the module in `slot`. + /// + /// # Signature + /// + /// ```text + /// eth2_callModule( + /// slot: u32, + /// name_offset: u32, + /// name_length: u32 + /// argument_offset: u32, + /// argument_length: u32, + /// return_offset: u32, + /// return_length: u32, + /// ) -> u32 + /// ``` + fn ext_call_module(&self, args: RuntimeArgs) -> ExtResult { + let memory = self.memory(); + + let slot: u32 = args.nth(0); + + let name_ptr: u32 = args.nth(1); + let name_len: u32 = args.nth(2); + 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(3); + let arg_len: u32 = args.nth(4); + + let ret_ptr: u32 = args.nth(5); + let ret_len: u32 = args.nth(6); + + let frame = StackFrame::builder() + .argument_offset(arg_ptr) + .argument_length(arg_len) + .return_offset(ret_ptr) + .return_length(ret_len) + .memory(memory) + .build(); + + // TODO: There's probably a bug here. It might be impossible to load a + // new module depending on the callstack. + + let children = self.0.children.borrow(); + let retcode = children[&slot].call(&name, frame); + + Ok(Some(retcode.into())) + } } #[derive(Debug)] @@ -363,15 +414,19 @@ struct Inner<'a> { instance: ModuleRef, buffer: RefCell, + children: RefCell>>, + call_targets: RefCell>, call_stack: RefCell>, } -impl<'a> Execute<'a> for RootRuntime<'a> { - fn execute(&'a mut self) -> [u8; 32] { +impl<'a> Execute for RootRuntime<'a> { + fn execute(&mut self) -> [u8; 32] { + let mut externals = RootExternals(self); + self.0 .instance - .invoke_export("main", &[], &mut self.externals()) + .invoke_export("main", &[], &mut externals) .expect("Executed 'main'"); *self.0.post_root.borrow() @@ -396,7 +451,8 @@ impl<'a, 'b> Externals for RootExternals<'a, 'b> { BUFFERSET_FUNC_INDEX => self.0.ext_buffer_set(args), BUFFERMERGE_FUNC_INDEX => self.0.ext_buffer_merge(args), BUFFERCLEAR_FUNC_INDEX => self.0.ext_buffer_clear(args), - EXEC_FUNC_INDEX => self.0.ext_exec(args), + LOADMODULE_FUNC_INDEX => self.0.ext_load_module(args), + CALLMODULE_FUNC_INDEX => self.0.ext_call_module(args), EXPOSE_FUNC_INDEX => self.0.ext_expose(args), ARGUMENT_FUNC_INDEX => self.0.ext_argument(args), RETURN_FUNC_INDEX => self.0.ext_return(args), @@ -454,8 +510,9 @@ mod test { runtime.0.call_stack.borrow_mut().push(frame); + let mut externals = RootExternals(&runtime); let result: u32 = Externals::invoke_index( - &mut runtime.externals(), + &mut externals, RETURN_FUNC_INDEX, [0.into(), 3.into()][..].into(), ) @@ -485,8 +542,9 @@ mod test { runtime.0.call_stack.borrow_mut().push(frame); + let mut externals = RootExternals(&runtime); let result: u32 = Externals::invoke_index( - &mut runtime.externals(), + &mut externals, RETURN_FUNC_INDEX, [0.into(), 1.into()][..].into(), ) @@ -514,8 +572,9 @@ mod test { runtime.0.call_stack.borrow_mut().push(frame); + let mut externals = RootExternals(&runtime); let result: u32 = Externals::invoke_index( - &mut runtime.externals(), + &mut externals, RETURN_FUNC_INDEX, [0.into(), 0.into()][..].into(), ) @@ -542,8 +601,9 @@ mod test { runtime.0.call_stack.borrow_mut().push(frame); + let mut externals = RootExternals(&runtime); let result: u32 = Externals::invoke_index( - &mut runtime.externals(), + &mut externals, ARGUMENT_FUNC_INDEX, [0.into(), 0.into()][..].into(), ) @@ -573,8 +633,9 @@ mod test { runtime.0.call_stack.borrow_mut().push(frame); + let mut externals = RootExternals(&runtime); let result: u32 = Externals::invoke_index( - &mut runtime.externals(), + &mut externals, ARGUMENT_FUNC_INDEX, [0.into(), 1.into()][..].into(), ) @@ -605,8 +666,9 @@ mod test { runtime.0.call_stack.borrow_mut().push(frame); + let mut externals = RootExternals(&runtime); let result: u32 = Externals::invoke_index( - &mut runtime.externals(), + &mut externals, ARGUMENT_FUNC_INDEX, [0.into(), 3.into()][..].into(), ) @@ -623,8 +685,9 @@ mod test { fn load_pre_state_root() { let runtime = build_runtime(&[], build_root(42), Buffer::default()); + let mut externals = RootExternals(&runtime); Externals::invoke_index( - &mut runtime.externals(), + &mut externals, LOADPRESTATEROOT_FUNC_INDEX, [0.into()][..].into(), ) @@ -640,8 +703,9 @@ mod test { let memory = runtime.memory(); memory.set(100, &build_root(42)).expect("sets memory"); + let mut externals = RootExternals(&runtime); Externals::invoke_index( - &mut runtime.externals(), + &mut externals, SAVEPOSTSTATEROOT_FUNC_INDEX, [100.into()][..].into(), ) @@ -654,14 +718,11 @@ mod test { fn block_data_size() { let runtime = build_runtime(&[1; 42], build_root(0), Buffer::default()); + let mut externals = RootExternals(&runtime); assert_eq!( - Externals::invoke_index( - &mut runtime.externals(), - BLOCKDATASIZE_FUNC_INDEX, - [][..].into() - ) - .unwrap() - .unwrap(), + Externals::invoke_index(&mut externals, BLOCKDATASIZE_FUNC_INDEX, [][..].into()) + .unwrap() + .unwrap(), 42.into() ); } @@ -671,15 +732,17 @@ mod test { let data: Vec = (1..21).collect(); let runtime = build_runtime(&data, build_root(0), Buffer::default()); + let mut externals = RootExternals(&runtime); Externals::invoke_index( - &mut runtime.externals(), + &mut externals, BLOCKDATACOPY_FUNC_INDEX, [1.into(), 0.into(), 20.into()][..].into(), ) .unwrap(); + let mut externals = RootExternals(&runtime); Externals::invoke_index( - &mut runtime.externals(), + &mut externals, BLOCKDATACOPY_FUNC_INDEX, [23.into(), 10.into(), 20.into()][..].into(), ) @@ -706,8 +769,9 @@ mod test { // Save the 32 byte key at position 0 in memory memory.set(0, &[1u8; 32]).unwrap(); + let mut externals = RootExternals(&runtime); Externals::invoke_index( - &mut runtime.externals(), + &mut externals, BUFFERGET_FUNC_INDEX, [0.into(), 0.into(), 32.into()][..].into(), ) @@ -727,8 +791,9 @@ mod test { memory.set(0, &[1u8; 32]).unwrap(); memory.set(32, &[2u8; 32]).unwrap(); + let mut externals = RootExternals(&runtime); Externals::invoke_index( - &mut runtime.externals(), + &mut externals, BUFFERSET_FUNC_INDEX, [0.into(), 0.into(), 32.into()][..].into(), ) @@ -749,8 +814,9 @@ mod test { let runtime = build_runtime(&[], build_root(0), buffer); + let mut externals = RootExternals(&runtime); Externals::invoke_index( - &mut runtime.externals(), + &mut externals, BUFFERMERGE_FUNC_INDEX, [1.into(), 2.into()][..].into(), ) @@ -773,8 +839,9 @@ mod test { let runtime = build_runtime(&[], build_root(0), buffer); + let mut externals = RootExternals(&runtime); Externals::invoke_index( - &mut runtime.externals(), + &mut externals, BUFFERCLEAR_FUNC_INDEX, [2.into()][..].into(), ) diff --git a/src/env/root/resolver.rs b/src/env/root/resolver.rs index 40f554b..6e3ee76 100644 --- a/src/env/root/resolver.rs +++ b/src/env/root/resolver.rs @@ -10,10 +10,11 @@ pub const BUFFERGET_FUNC_INDEX: usize = 4; pub const BUFFERSET_FUNC_INDEX: usize = 5; pub const BUFFERMERGE_FUNC_INDEX: usize = 6; pub const BUFFERCLEAR_FUNC_INDEX: usize = 7; -pub const EXEC_FUNC_INDEX: usize = 8; +pub const LOADMODULE_FUNC_INDEX: usize = 8; pub const EXPOSE_FUNC_INDEX: usize = 9; pub const ARGUMENT_FUNC_INDEX: usize = 10; pub const RETURN_FUNC_INDEX: usize = 11; +pub const CALLMODULE_FUNC_INDEX: usize = 12; pub struct RuntimeModuleImportResolver; @@ -59,9 +60,13 @@ impl<'a> ModuleImportResolver for RuntimeModuleImportResolver { Signature::new(&[ValueType::I32][..], None), BUFFERCLEAR_FUNC_INDEX, ), - "eth2_exec" => FuncInstance::alloc_host( - Signature::new(&[ValueType::I32, ValueType::I32][..], None), - EXEC_FUNC_INDEX, + "eth2_loadModule" => FuncInstance::alloc_host( + Signature::new(&[ValueType::I32; 3][..], None), + LOADMODULE_FUNC_INDEX, + ), + "eth2_callModule" => FuncInstance::alloc_host( + Signature::new(&[ValueType::I32; 7][..], Some(ValueType::I32)), + CALLMODULE_FUNC_INDEX, ), "eth2_expose" => FuncInstance::alloc_host( Signature::new(&[ValueType::I32, ValueType::I32][..], None), diff --git a/src/execute.rs b/src/execute.rs index ef027bd..aaa8f28 100644 --- a/src/execute.rs +++ b/src/execute.rs @@ -1,3 +1,3 @@ -pub trait Execute<'a> { - fn execute(&'a mut self) -> [u8; 32]; +pub trait Execute { + fn execute(&mut self) -> [u8; 32]; } diff --git a/tests/child_functions.rs b/tests/child_functions.rs index dd84441..5984a0f 100644 --- a/tests/child_functions.rs +++ b/tests/child_functions.rs @@ -9,36 +9,62 @@ fn compile_wat(child_code: &str) -> Vec { wat2wasm(format!( r#" - (module - (import "env" "eth2_exec" (func $exec (param i32) (param i32))) - (import "env" "eth2_expose" (func $expose (param i32) (param i32))) - (import "env" "eth2_return" (func $return (param i32) (param i32) (result i32))) - (import "env" "eth2_argument" (func $argument (param i32) (param i32) (result i32))) + (module + (import "env" "eth2_loadModule" (func $load (param i32) (param i32) (param i32))) + (import "env" "eth2_expose" (func $expose (param i32) (param i32))) + (import "env" "eth2_return" (func $return (param i32) (param i32) (result i32))) + (import "env" "eth2_argument" (func $argument (param i32) (param i32) (result i32))) + (import + "env" + "eth2_callModule" + (func + $call + (param i32) + (param i32) + (param i32) + (param i32) + (param i32) + (param i32) + (param i32) + (result i32))) - (memory (export "memory") 1) - (data (i32.const 0) "some_func") - (data (i32.const 10) "{}") - (func $some_func - (export "some_func") - (param i32) - (param i32) - (result i32) + (memory (export "memory") 1) + (data (i32.const 0) "some_func") + (data (i32.const 10) "main") + (data (i32.const 14) "{}") + (func $some_func + (export "some_func") + (result i32) - (; Read the argument, and check the result ;) - (drop (call $argument (i32.const 89) (i32.const 4))) - (if (i32.ne (i32.load (i32.const 89)) (i32.const 9999)) - (then (unreachable))) + (; Read the argument, and check the result ;) + (drop (call $argument (i32.const 89) (i32.const 4))) + (if (i32.ne (i32.load (i32.const 89)) (i32.const 9999)) + (then (unreachable))) - (; Return a value to the caller ;) - (i32.store (i32.const 99) (i32.const 8888)) - (drop (call $return (i32.const 99) (i32.const 4))) + (; Return a value to the caller ;) + (i32.store (i32.const 99) (i32.const 8888)) + (drop (call $return (i32.const 99) (i32.const 4))) - (i32.const 6654)) + (i32.const 6654)) - (func $main (export "main") - (call $expose (i32.const 0) (i32.const 9)) - (call $exec (i32.const 10) (i32.const {}))) - )"#, + (func $main (export "main") + (call $expose (i32.const 0) (i32.const 9)) + (call $load (i32.const 0) (i32.const 14) (i32.const {})) + (drop + (call + $call + (i32.const 0) (; Slot ;) + (i32.const 10) (; Name Offset ;) + (i32.const 4) (; Name Length ;) + (i32.const 0) (; Argument Offset ;) + (i32.const 0) (; Argument Length ;) + (i32.const 0) (; Return Offset ;) + (i32.const 0) (; Return Length ;) + ) + ) + ) + ) + "#, escape(&child_asm), child_asm.len(), )) @@ -63,7 +89,7 @@ fn call() { (result i32))) (memory (export "memory") 1) (data (i32.const 0) "some_func") - (func $main (export "main") (local $x i32) + (func $main (export "main") (result i32) (local $x i32) (i32.store (i32.const 10) (i32.const 9999)) (set_local $x (call @@ -79,7 +105,12 @@ fn call() { (then (unreachable))) (if (i32.ne (i32.load (i32.const 15)) (i32.const 8888)) - (then (unreachable))))) + (then (unreachable)) + ) + + (i32.const 6301) + ) + ) "#; let code = compile_wat(child_code); diff --git a/tests/host_functions.rs b/tests/host_functions.rs index f4c96b2..4409de9 100644 --- a/tests/host_functions.rs +++ b/tests/host_functions.rs @@ -13,17 +13,16 @@ fn compile_wat(code: &str) -> Vec { [ r#" (module - (import "env" "eth2_savePostStateRoot" (func $save_post_root (param i32))) - (import "env" "eth2_loadPreStateRoot" (func $load_pre_root (param i32))) - (import "env" "eth2_blockDataSize" (func $block_data_size (result i32))) - (import "env" "eth2_blockDataCopy" (func $block_data_copy (param i32) (param i32) (param i32))) - (import "env" "eth2_bufferGet" (func $buffer_get (param i32) (param i32) (param i32) (result i32))) - (import "env" "eth2_bufferSet" (func $buffer_set (param i32) (param i32) (param i32))) - (import "env" "eth2_bufferMerge" (func $buffer_merge (param i32) (param i32))) - (import "env" "eth2_bufferClear" (func $buffer_clear (param i32))) - (import "env" "eth2_exec" (func $exec (param i32) (param i32))) - (memory (export "memory") 1) - (func $main (export "main") + (import "env" "eth2_savePostStateRoot" (func $save_post_root (param i32))) + (import "env" "eth2_loadPreStateRoot" (func $load_pre_root (param i32))) + (import "env" "eth2_blockDataSize" (func $block_data_size (result i32))) + (import "env" "eth2_blockDataCopy" (func $block_data_copy (param i32) (param i32) (param i32))) + (import "env" "eth2_bufferGet" (func $buffer_get (param i32) (param i32) (param i32) (result i32))) + (import "env" "eth2_bufferSet" (func $buffer_set (param i32) (param i32) (param i32))) + (import "env" "eth2_bufferMerge" (func $buffer_merge (param i32) (param i32))) + (import "env" "eth2_bufferClear" (func $buffer_clear (param i32))) + (memory (export "memory") 1) + (func $main (export "main") "#, code, r#"))"#, @@ -40,18 +39,32 @@ fn build_root(n: u8) -> [u8; 32] { } #[test] -fn exec() { +fn module_load_and_call() { let child_code = nop(); let code = wat2wasm(format!( r#" - (module - (import "env" "eth2_exec" (func $exec (param i32) (param i32))) - (memory (export "memory") 1) - (data (i32.const 0) "{}") - (func $main (export "main") - (call $exec (i32.const 0) (i32.const {})) - ))"#, + (module + (import "env" "eth2_loadModule" (func $load (param i32) (param i32) (param i32))) + (import + "env" + "eth2_callModule" + (func + $call + (param i32) + (param i32) + (param i32) + (param i32) + (param i32) + (param i32) + (param i32) + (result i32))) + (memory (export "memory") 1) + (data (i32.const 0) "{}") + (func $main (export "main") + (; Load a compiled module into slot 0 ;) + (call $load (i32.const 0) (i32.const 0) (i32.const {})))) + "#, escape(&child_code), child_code.len(), )) From 446e68dc2c8dd0f58670abde868fa4652775133b Mon Sep 17 00:00:00 2001 From: Sam Wilson Date: Sun, 10 Nov 2019 12:23:29 -0500 Subject: [PATCH 7/7] Implement return/argument for child runtimes --- src/env/child/mod.rs | 50 +++++++++++++++++++++++++++++++++++++++ src/env/child/resolver.rs | 12 ++++++++++ src/env/mod.rs | 40 ++++++++++++++++++++++++++++++- src/env/root/mod.rs | 30 +++++------------------ tests/child_functions.rs | 44 +++++++++++++++++++++++++++++----- 5 files changed, 145 insertions(+), 31 deletions(-) diff --git a/src/env/child/mod.rs b/src/env/child/mod.rs index bd1f0ac..5b5bbdf 100644 --- a/src/env/child/mod.rs +++ b/src/env/child/mod.rs @@ -102,6 +102,54 @@ impl<'a> ChildRuntime<'a> { 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)] @@ -115,6 +163,8 @@ impl<'a, 'b> Externals for ChildExternals<'a, 'b> { ) -> Result, 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"), } } diff --git a/src/env/child/resolver.rs b/src/env/child/resolver.rs index 7ff8980..09a5809 100644 --- a/src/env/child/resolver.rs +++ b/src/env/child/resolver.rs @@ -1,5 +1,7 @@ pub mod externals { pub const CALL: usize = 1; + pub const ARGUMENT: usize = 2; + pub const RETURN: usize = 3; } use wasmi::{ @@ -15,6 +17,16 @@ impl<'a> ModuleImportResolver for ChildModuleImportResolver { _signature: &Signature, ) -> Result { 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)), diff --git a/src/env/mod.rs b/src/env/mod.rs index 1b45ce8..32230e7 100644 --- a/src/env/mod.rs +++ b/src/env/mod.rs @@ -3,7 +3,7 @@ pub mod root; use typed_builder::TypedBuilder; -use wasmi::{MemoryRef, RuntimeValue, Trap}; +use wasmi::{MemoryInstance, MemoryRef, RuntimeValue, Trap}; pub type ExtResult = Result, Trap>; @@ -17,3 +17,41 @@ struct StackFrame { return_offset: u32, return_length: u32, } + +impl StackFrame { + pub fn transfer_argument( + &self, + dest: &MemoryRef, + dest_ptr: u32, + dest_len: u32, + ) -> Result { + 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 { + 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) + } +} diff --git a/src/env/root/mod.rs b/src/env/root/mod.rs index 17ba2d5..c8b9708 100644 --- a/src/env/root/mod.rs +++ b/src/env/root/mod.rs @@ -23,8 +23,8 @@ use std::rc::{Rc, Weak}; use super::{ExtResult, StackFrame}; use wasmi::{ - Externals, FuncInstance, ImportsBuilder, MemoryInstance, MemoryRef, Module, ModuleInstance, - ModuleRef, RuntimeArgs, RuntimeValue, Trap, + Externals, FuncInstance, ImportsBuilder, MemoryRef, Module, ModuleInstance, ModuleRef, + RuntimeArgs, RuntimeValue, Trap, }; #[derive(Debug, Clone)] @@ -123,18 +123,9 @@ impl<'a> RootRuntime<'a> { .last() .expect("eth2_return requires a call stack"); - let len = std::cmp::min(src_len, top.return_length); + let len = top.transfer_return(&memory, src_ptr, src_len).unwrap(); - MemoryInstance::transfer( - &memory, - src_ptr as usize, - &top.memory, - top.return_offset as usize, - len as usize, - ) - .unwrap(); - - Ok(Some(top.return_length.into())) + Ok(Some(len.into())) } /// Copies the argument data from the most recent call into memory at the @@ -156,18 +147,9 @@ impl<'a> RootRuntime<'a> { .last() .expect("eth2_argument requires a call stack"); - let len = std::cmp::min(dest_len, top.argument_length); - - MemoryInstance::transfer( - &top.memory, - top.argument_offset as usize, - &memory, - dest_ptr as usize, - len as usize, - ) - .unwrap(); + let len = top.transfer_argument(&memory, dest_ptr, dest_len).unwrap(); - Ok(Some(top.argument_length.into())) + Ok(Some(len.into())) } fn ext_expose(&self, args: RuntimeArgs) -> ExtResult { diff --git a/tests/child_functions.rs b/tests/child_functions.rs index 5984a0f..f04e8d3 100644 --- a/tests/child_functions.rs +++ b/tests/child_functions.rs @@ -31,7 +31,7 @@ fn compile_wat(child_code: &str) -> Vec { (memory (export "memory") 1) (data (i32.const 0) "some_func") (data (i32.const 10) "main") - (data (i32.const 14) "{}") + (data (i32.const 22) "{}") (func $some_func (export "some_func") (result i32) @@ -49,19 +49,25 @@ fn compile_wat(child_code: &str) -> Vec { (func $main (export "main") (call $expose (i32.const 0) (i32.const 9)) - (call $load (i32.const 0) (i32.const 14) (i32.const {})) + (call $load (i32.const 0) (i32.const 22) (i32.const {})) + (i32.store (i32.const 14) (i32.const 1234)) (drop (call $call (i32.const 0) (; Slot ;) (i32.const 10) (; Name Offset ;) (i32.const 4) (; Name Length ;) - (i32.const 0) (; Argument Offset ;) - (i32.const 0) (; Argument Length ;) - (i32.const 0) (; Return Offset ;) - (i32.const 0) (; Return Length ;) + (i32.const 14) (; Argument Offset ;) + (i32.const 4) (; Argument Length ;) + (i32.const 18) (; Return Offset ;) + (i32.const 4) (; Return Length ;) ) ) + + (; Check the returned buffer from the child runtime ;) + (if + (i32.ne (i32.load (i32.const 18)) (i32.const 4321)) + (then (unreachable))) ) ) "#, @@ -75,6 +81,22 @@ fn compile_wat(child_code: &str) -> Vec { fn call() { let child_code = r#" (module + (import + "env" + "eth2_return" + (func + $eth2_return + (param i32) + (param i32) + (result i32))) + (import + "env" + "eth2_argument" + (func + $eth2_argument + (param i32) + (param i32) + (result i32))) (import "env" "eth2_call" @@ -90,6 +112,16 @@ fn call() { (memory (export "memory") 1) (data (i32.const 0) "some_func") (func $main (export "main") (result i32) (local $x i32) + (; Check that the argument provided by the caller is 1234 ;) + (drop (call $eth2_argument (i32.const 10) (i32.const 4))) + (if + (i32.ne (i32.load (i32.const 10)) (i32.const 1234)) + (then (unreachable))) + + (; Return a value to the caller ;) + (i32.store (i32.const 10) (i32.const 4321)) + (drop (call $eth2_return (i32.const 10) (i32.const 4))) + (i32.store (i32.const 10) (i32.const 9999)) (set_local $x (call