From dbc72bb098abfde695e61a50b2fdc4886d905b62 Mon Sep 17 00:00:00 2001 From: Jean-Pierre de Villiers Date: Thu, 27 Jan 2022 03:58:37 +0000 Subject: [PATCH 1/4] chore: calculate input data size and allocate pages accordingly --- wasmi-impl/src/lib.rs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/wasmi-impl/src/lib.rs b/wasmi-impl/src/lib.rs index 9b598a7..b17fd5a 100644 --- a/wasmi-impl/src/lib.rs +++ b/wasmi-impl/src/lib.rs @@ -23,8 +23,13 @@ pub fn exec_wasm_with_data( ) -> Result, ExecWasmError> { let module = wasmi::Module::from_buffer(binary)?; - // TODO: Calculate the memory size always be larger than `data` - let mem_instance = MemoryInstance::alloc(Pages(200), None)?; + // Calculate the memory size always be larger than `data` + let data_size = &data.len(); + let num_pages = match data_size % (64 * 1024) { + 0 => data_size / (64 * 1024), + _ => data_size / (64 * 1024) + 1, + }; + let mem_instance = MemoryInstance::alloc(Pages(num_pages), Pages(num_pages))?; // TODO: Error Handling mem_instance.set(0, data).unwrap(); From 4c29e089258f61d5c6a7911a3c4f0a5f955164be Mon Sep 17 00:00:00 2001 From: Jean-Pierre de Villiers Date: Mon, 31 Jan 2022 09:26:01 +0000 Subject: [PATCH 2/4] chore: calculate pages needed for the input/output buffer in memory A new struct `Proc` is defined for output purposes. Due to crossing an FFI boundary this will have to change. --- wasmi-impl/src/lib.rs | 68 ++++++++++++++++++++++++++++++++++--------- 1 file changed, 54 insertions(+), 14 deletions(-) diff --git a/wasmi-impl/src/lib.rs b/wasmi-impl/src/lib.rs index b17fd5a..06ec0ff 100644 --- a/wasmi-impl/src/lib.rs +++ b/wasmi-impl/src/lib.rs @@ -1,10 +1,21 @@ #![no_std] +extern crate alloc; + +use core::cmp; +use alloc::vec::{self, Vec}; use wasmi::{ - self, memory_units::Pages, ExternVal, ImportsBuilder, MemoryInstance, ModuleInstance, - NopExternals, RuntimeValue, + self, memory_units::Pages, ExternVal, ImportsBuilder, MemoryInstance, + ModuleInstance, NopExternals, RuntimeValue, }; static ENTRYPOINT: &str = "exec"; +static MAX_MEMORY_KIB: usize = 128 * 1024; +static MAX_PAGES: usize = match MAX_MEMORY_KIB % 64 { + 0 => MAX_MEMORY_KIB / 64, + _ => MAX_MEMORY_KIB / 64 + 1, +}; +static MEM_SMALL_BYTES: usize = 128 / 8; +static PAGE_SIZE_BYTES: usize = 64 * 1024; #[derive(Debug)] pub enum ExecWasmError { @@ -17,33 +28,62 @@ impl From for ExecWasmError { } } -pub fn exec_wasm_with_data( +struct Proc { + out_buffer: Vec, + return_code: Option, +} + +impl<'a> Proc { + pub fn bytes(&'a self) -> impl 'a + Iterator { + self.out_buffer.iter() + } +} + +pub fn exec_wasm_with_data<'a>( binary: &[u8], data: &[u8], -) -> Result, ExecWasmError> { + out_size: usize, /* output size in bytes */ +) -> Result { let module = wasmi::Module::from_buffer(binary)?; - // Calculate the memory size always be larger than `data` - let data_size = &data.len(); - let num_pages = match data_size % (64 * 1024) { - 0 => data_size / (64 * 1024), - _ => data_size / (64 * 1024) + 1, - }; - let mem_instance = MemoryInstance::alloc(Pages(num_pages), Pages(num_pages))?; + let data_size = data.len(); + + /* + * Calculate the memory size to always be larger than `data`. Small + * values (a WASM numeric vector or smaller) should always fit. + */ + let calc_rem = |num_bytes| cmp::max(num_bytes, MEM_SMALL_BYTES) % PAGE_SIZE_BYTES; + let calc_pages = |num_bytes| cmp::max(num_bytes, MEM_SMALL_BYTES) / PAGE_SIZE_BYTES; + let combined_size = data_size + out_size; + let num_pages = cmp::min( + MAX_PAGES, + match calc_rem(combined_size) { + 0 => calc_pages(combined_size), + _ => calc_pages(combined_size) + 1, + }, + ); + + let mem_instance = MemoryInstance::alloc(Pages(num_pages), Some(Pages(MAX_PAGES)))?; // TODO: Error Handling mem_instance.set(0, data).unwrap(); - let imports = [ExternVal::Memory(mem_instance)]; + let imports = [ExternVal::Memory(mem_instance.clone())]; // TODO: This panics. We probably want to run start functions let instance = ModuleInstance::with_externvals(&module, imports.iter())?.assert_no_start(); - Ok(instance.invoke_export( + let return_code = instance.invoke_export( ENTRYPOINT, &[RuntimeValue::I32(0), RuntimeValue::I32(data.len() as i32)], &mut NopExternals, - )?) + )?; + let proc = Proc { + out_buffer: vec![0u8; out_size], + return_code, + }; + mem_instance.get_into(data_size as u32, proc.out_buffer.as_mut_slice()); + Ok(proc) } pub fn exec_wasm(binary: &[u8]) -> Result, ExecWasmError> { From d4871a3cbab7edb704c65507ad4715b5e4abee22 Mon Sep 17 00:00:00 2001 From: Jean-Pierre de Villiers Date: Mon, 31 Jan 2022 11:12:28 +0000 Subject: [PATCH 3/4] fix(wasmi-impl): fix compilation, vec import & variable mutability --- wasmi-impl/src/lib.rs | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/wasmi-impl/src/lib.rs b/wasmi-impl/src/lib.rs index 06ec0ff..f80bc19 100644 --- a/wasmi-impl/src/lib.rs +++ b/wasmi-impl/src/lib.rs @@ -2,7 +2,8 @@ extern crate alloc; use core::cmp; -use alloc::vec::{self, Vec}; +use alloc::vec::Vec; +use alloc::vec; use wasmi::{ self, memory_units::Pages, ExternVal, ImportsBuilder, MemoryInstance, ModuleInstance, NopExternals, RuntimeValue, @@ -28,9 +29,9 @@ impl From for ExecWasmError { } } -struct Proc { - out_buffer: Vec, - return_code: Option, +pub struct Proc { + pub out_buffer: Vec, + pub return_code: Option, } impl<'a> Proc { @@ -39,7 +40,7 @@ impl<'a> Proc { } } -pub fn exec_wasm_with_data<'a>( +pub fn exec_wasm_with_data ( binary: &[u8], data: &[u8], out_size: usize, /* output size in bytes */ @@ -78,11 +79,11 @@ pub fn exec_wasm_with_data<'a>( &[RuntimeValue::I32(0), RuntimeValue::I32(data.len() as i32)], &mut NopExternals, )?; - let proc = Proc { + let mut proc = Proc { out_buffer: vec![0u8; out_size], return_code, }; - mem_instance.get_into(data_size as u32, proc.out_buffer.as_mut_slice()); + mem_instance.get_into(data_size as u32, proc.out_buffer.as_mut_slice())?; Ok(proc) } From 3b1998814b0bb8540c53a3111938afabc237cf75 Mon Sep 17 00:00:00 2001 From: Jean-Pierre de Villiers Date: Wed, 2 Feb 2022 06:03:07 +0000 Subject: [PATCH 4/4] fix(wasmi-impl): memory calculation is now simpler and more robust The `wasmi` library provides functionality for rounding up units of memory and this is now properly utilised. Error handling for this calculation has now also been implemented. --- wasmi-impl/src/lib.rs | 88 ++++++++++++++++++++++--------------------- 1 file changed, 46 insertions(+), 42 deletions(-) diff --git a/wasmi-impl/src/lib.rs b/wasmi-impl/src/lib.rs index f80bc19..b49f263 100644 --- a/wasmi-impl/src/lib.rs +++ b/wasmi-impl/src/lib.rs @@ -1,26 +1,24 @@ #![no_std] extern crate alloc; -use core::cmp; -use alloc::vec::Vec; use alloc::vec; +use alloc::vec::Vec; use wasmi::{ - self, memory_units::Pages, ExternVal, ImportsBuilder, MemoryInstance, - ModuleInstance, NopExternals, RuntimeValue, + self, + memory_units::{Bytes, Pages, RoundUpTo}, + ExternVal, ImportsBuilder, MemoryInstance, ModuleInstance, NopExternals, RuntimeValue, }; static ENTRYPOINT: &str = "exec"; -static MAX_MEMORY_KIB: usize = 128 * 1024; -static MAX_PAGES: usize = match MAX_MEMORY_KIB % 64 { - 0 => MAX_MEMORY_KIB / 64, - _ => MAX_MEMORY_KIB / 64 + 1, -}; -static MEM_SMALL_BYTES: usize = 128 / 8; -static PAGE_SIZE_BYTES: usize = 64 * 1024; + +const MAX_MEMORY_KIB: usize = 128 * 1024; +const MAX_PAGES: Pages = Pages(MAX_MEMORY_KIB); #[derive(Debug)] pub enum ExecWasmError { WasmiError(wasmi::Error), + WasmiReturn(i32), + InvalidModule, } impl From for ExecWasmError { @@ -29,42 +27,45 @@ impl From for ExecWasmError { } } -pub struct Proc { - pub out_buffer: Vec, - pub return_code: Option, +pub struct WasmOutput { + out_buffer: Vec, + return_code: Option, } -impl<'a> Proc { - pub fn bytes(&'a self) -> impl 'a + Iterator { - self.out_buffer.iter() +impl WasmOutput { + pub fn out_buffer(&self) -> &[u8] { + self.out_buffer.as_slice() + } + pub fn return_code(&self) -> Option { + self.return_code } } -pub fn exec_wasm_with_data ( +pub fn calc_num_pages(data_size: usize, out_size: usize) -> Result { + let combined_size = match data_size.checked_add(out_size) { + None => Err(ExecWasmError::InvalidModule), + Some(size) => Ok(Bytes(size)), + }?; + + let num_pages: Pages = combined_size.round_up_to(); + if num_pages > MAX_PAGES { + Err(ExecWasmError::InvalidModule) + } else { + Ok(num_pages) + } +} + +pub fn exec_wasm_with_data( binary: &[u8], data: &[u8], - out_size: usize, /* output size in bytes */ -) -> Result { + out_size: usize, +) -> Result, ExecWasmError> { let module = wasmi::Module::from_buffer(binary)?; let data_size = data.len(); - /* - * Calculate the memory size to always be larger than `data`. Small - * values (a WASM numeric vector or smaller) should always fit. - */ - let calc_rem = |num_bytes| cmp::max(num_bytes, MEM_SMALL_BYTES) % PAGE_SIZE_BYTES; - let calc_pages = |num_bytes| cmp::max(num_bytes, MEM_SMALL_BYTES) / PAGE_SIZE_BYTES; - let combined_size = data_size + out_size; - let num_pages = cmp::min( - MAX_PAGES, - match calc_rem(combined_size) { - 0 => calc_pages(combined_size), - _ => calc_pages(combined_size) + 1, - }, - ); - - let mem_instance = MemoryInstance::alloc(Pages(num_pages), Some(Pages(MAX_PAGES)))?; + let num_pages = calc_num_pages(data_size, out_size)?; + let mem_instance = MemoryInstance::alloc(num_pages, Some(MAX_PAGES))?; // TODO: Error Handling mem_instance.set(0, data).unwrap(); @@ -79,12 +80,15 @@ pub fn exec_wasm_with_data ( &[RuntimeValue::I32(0), RuntimeValue::I32(data.len() as i32)], &mut NopExternals, )?; - let mut proc = Proc { - out_buffer: vec![0u8; out_size], - return_code, - }; - mem_instance.get_into(data_size as u32, proc.out_buffer.as_mut_slice())?; - Ok(proc) + match return_code { + Some(RuntimeValue::I32(0)) => { + let mut wasm_output = vec![0u8; out_size]; + mem_instance.get_into(data_size as u32, wasm_output.as_mut_slice())?; + Ok(wasm_output) + } + Some(RuntimeValue::I32(code)) => Err(ExecWasmError::WasmiReturn(code)), + _ => Err(ExecWasmError::InvalidModule), + } } pub fn exec_wasm(binary: &[u8]) -> Result, ExecWasmError> {