diff --git a/ext/src/main.rs b/ext/src/main.rs index 3c74790..99c7e22 100644 --- a/ext/src/main.rs +++ b/ext/src/main.rs @@ -5,18 +5,18 @@ use std::env; fn main() { - wasm_utils::init_log(); + wasm_utils::init_log(); - let args = env::args().collect::>(); - if args.len() != 3 { - println!("Usage: {} input_file.wasm output_file.wasm", args[0]); - return; - } + let args = env::args().collect::>(); + if args.len() != 3 { + println!("Usage: {} input_file.wasm output_file.wasm", args[0]); + return; + } - let module = wasm_utils::externalize( - parity_wasm::deserialize_file(&args[1]).expect("Module to deserialize ok"), - vec!["_free", "_malloc"], - ); + let module = wasm_utils::externalize( + parity_wasm::deserialize_file(&args[1]).expect("Module to deserialize ok"), + vec!["_free", "_malloc"], + ); - parity_wasm::serialize_to_file(&args[2], module).expect("Module to serialize ok"); + parity_wasm::serialize_to_file(&args[2], module).expect("Module to serialize ok"); } diff --git a/gas/src/main.rs b/gas/src/main.rs index 27371da..d1cd5e2 100644 --- a/gas/src/main.rs +++ b/gas/src/main.rs @@ -5,18 +5,18 @@ use std::env; fn main() { - wasm_utils::init_log(); + wasm_utils::init_log(); - let args = env::args().collect::>(); - if args.len() != 3 { - println!("Usage: {} input_file.wasm output_file.wasm", args[0]); - return; - } + let args = env::args().collect::>(); + if args.len() != 3 { + println!("Usage: {} input_file.wasm output_file.wasm", args[0]); + return; + } - // Loading module - let module = parity_wasm::deserialize_file(&args[1]).expect("Module deserialization to succeed"); + // Loading module + let module = parity_wasm::deserialize_file(&args[1]).expect("Module deserialization to succeed"); - let result = wasm_utils::inject_gas_counter(module); + let result = wasm_utils::inject_gas_counter(module); - parity_wasm::serialize_to_file(&args[2], result).expect("Module serialization to succeed") + parity_wasm::serialize_to_file(&args[2], result).expect("Module serialization to succeed") } diff --git a/rust-runner/src/alloc.rs b/rust-runner/src/alloc.rs index 63c0eff..095e179 100644 --- a/rust-runner/src/alloc.rs +++ b/rust-runner/src/alloc.rs @@ -2,26 +2,26 @@ use parity_wasm::interpreter::{self, ModuleInstance}; use runtime::Runtime; pub struct Arena { - pub runtime: Runtime, + pub runtime: Runtime, } #[derive(Debug)] pub struct Error; impl Arena { - pub fn alloc(&self, size: u32) -> Result { - // todo: maybe use unsafe cell since it has nothing to do with threads - let previous_top = self.runtime.env().dynamic_top.get(); - self.runtime.env().dynamic_top.set(previous_top + size); - Ok(previous_top) - } + pub fn alloc(&self, size: u32) -> Result { + // todo: maybe use unsafe cell since it has nothing to do with threads + let previous_top = self.runtime.env().dynamic_top.get(); + self.runtime.env().dynamic_top.set(previous_top + size); + Ok(previous_top) + } } impl interpreter::UserFunctionInterface for Arena { - fn call(&mut self, _module: &ModuleInstance, context: interpreter::CallerContext) -> Result, interpreter::Error> { - let amount = context.value_stack.pop_as::()?; - self.alloc(amount as u32) - .map(|val| Some((val as i32).into())) - .map_err(|e| interpreter::Error::Trap(format!("Allocator failure: {}", "todo: format arg"))) - } + fn call(&mut self, _module: &ModuleInstance, context: interpreter::CallerContext) -> Result, interpreter::Error> { + let amount = context.value_stack.pop_as::()?; + self.alloc(amount as u32) + .map(|val| Some((val as i32).into())) + .map_err(|e| interpreter::Error::Trap(format!("Allocator failure: {}", "todo: format arg"))) + } } \ No newline at end of file diff --git a/rust-runner/src/call_args.rs b/rust-runner/src/call_args.rs index ec06680..c36b4e5 100644 --- a/rust-runner/src/call_args.rs +++ b/rust-runner/src/call_args.rs @@ -1,82 +1,69 @@ -use parity_wasm::interpreter::{self, ModuleInstanceInterface}; -use {alloc, runtime}; +use parity_wasm::interpreter; +use runtime; -use {DEFAULT_MEMORY_INDEX, WasmMemoryPtr}; +use WasmMemoryPtr; fn write_u32(dst: &mut [u8], val: u32) { - dst[0] = (val & 0x000000ff) as u8; - dst[1] = ((val & 0x0000ff00) >> 8) as u8; - dst[2] = ((val & 0x00ff0000) >> 16) as u8; - dst[3] = ((val & 0xff000000) >> 24) as u8; + dst[0] = (val & 0x000000ff) as u8; + dst[1] = ((val & 0x0000ff00) >> 8) as u8; + dst[2] = ((val & 0x00ff0000) >> 16) as u8; + dst[3] = ((val & 0xff000000) >> 24) as u8; } #[derive(Debug)] pub enum Error { - Allocator(alloc::Error), - Interpreter(interpreter::Error), + Allocator(runtime::ErrorAlloc), + Interpreter(interpreter::Error), } -impl From for Error { - fn from(err: alloc::Error) -> Self { - Error::Allocator(err) - } +impl From for Error { + fn from(err: runtime::ErrorAlloc) -> Self { + Error::Allocator(err) + } } impl From for Error { - fn from(err: interpreter::Error) -> Self { - Error::Interpreter(err) - } + fn from(err: interpreter::Error) -> Self { + Error::Interpreter(err) + } } pub fn init( - env: &interpreter::ModuleInstanceInterface, - runtime: &runtime::Runtime, - input: &[u8], + memory: &interpreter::MemoryInstance, + runtime: &mut runtime::Runtime, + input: &[u8], ) -> Result { - let mut input_ptr_slc = [0u8; 4]; - let mut input_length = [0u8; 4]; + let mut input_ptr_slc = [0u8; 4]; + let mut input_length = [0u8; 4]; - let allocator = runtime.allocator(); + let descriptor_ptr = runtime.alloc(16)?; - let descriptor_ptr = allocator.alloc(16)?; + println!("descriptor_ptr: {}", descriptor_ptr); - println!("descriptor_ptr: {}", descriptor_ptr); - let memory = env.memory(DEFAULT_MEMORY_INDEX)?; + if input.len() > 0 { + let input_ptr = runtime.alloc(input.len() as u32)?; + write_u32(&mut input_ptr_slc, input_ptr); + write_u32(&mut input_length, input.len() as u32); + memory.set(input_ptr, input)?; + println!("input_ptr: {}", input_ptr); + } else { + write_u32(&mut input_ptr_slc, 0); + write_u32(&mut input_length, 0); + } - if input.len() > 0 { - let input_ptr = allocator.alloc(input.len() as u32)?; - write_u32(&mut input_ptr_slc, input_ptr); - write_u32(&mut input_length, input.len() as u32); - memory.set(input_ptr, input)?; - println!("input_ptr: {}", input_ptr); - } else { - write_u32(&mut input_ptr_slc, 0); - write_u32(&mut input_length, 0); - } + memory.set(descriptor_ptr, &input_ptr_slc)?; + memory.set(descriptor_ptr+4, &input_length)?; - memory.set(descriptor_ptr, &input_ptr_slc)?; - memory.set(descriptor_ptr+4, &input_length)?; + // zero result ptr/len + memory.set(descriptor_ptr+8, &[0u8; 4])?; + memory.set(descriptor_ptr+12, &[0u8; 4])?; - // zero result ptr/len - memory.set(descriptor_ptr+8, &[0u8; 4])?; - memory.set(descriptor_ptr+12, &[0u8; 4])?; + println!("descriptor: {:?}", memory.get(descriptor_ptr, 16)); - println!("descriptor: {:?}", memory.get(descriptor_ptr, 16)); - - Ok(descriptor_ptr as i32) + Ok(descriptor_ptr as i32) } -fn read_u32(slc: &[u8]) -> u32 { - use std::ops::Shl; - (slc[0] as u32) + (slc[1] as u32).shl(8) + (slc[2] as u32).shl(16) + (slc[3] as u32).shl(24) -} - -// pub fn retrieve(env: -// env: &interpreter::ModuleInstanceInterface, -// runtime: &runtime::Runtime, -// descriptor_ptr: u32, -// result_ptr: u32, -// ) -> Result, Error> { -// let memory = env.memory(DEFAULT_MEMORY_INDEX)?; - -// } \ No newline at end of file +fn _read_u32(slc: &[u8]) -> u32 { + use std::ops::Shl; + (slc[0] as u32) + (slc[1] as u32).shl(8) + (slc[2] as u32).shl(16) + (slc[3] as u32).shl(24) +} \ No newline at end of file diff --git a/rust-runner/src/gas_counter.rs b/rust-runner/src/gas_counter.rs deleted file mode 100644 index 2dc3e84..0000000 --- a/rust-runner/src/gas_counter.rs +++ /dev/null @@ -1,20 +0,0 @@ -use parity_wasm::interpreter::{self, ModuleInstance}; -use runtime::Runtime; - -pub struct GasCounter { - pub runtime: Runtime, -} - -impl interpreter::UserFunctionInterface for GasCounter { - fn call(&mut self, _module: &ModuleInstance, context: interpreter::CallerContext) -> Result, interpreter::Error> { - let prev = self.runtime.env().gas_counter.get(); - let update = context.value_stack.pop_as::()? as u64; - if prev + update > self.runtime.env().gas_limit { - // exceeds gas - Err(interpreter::Error::Trap(format!("Gas exceeds limits of {}", self.runtime.env().gas_limit))) - } else { - self.runtime.env().gas_counter.set(prev + update); - Ok(None) - } - } -} \ No newline at end of file diff --git a/rust-runner/src/main.rs b/rust-runner/src/main.rs index d9c0f62..4bb561e 100644 --- a/rust-runner/src/main.rs +++ b/rust-runner/src/main.rs @@ -1,19 +1,17 @@ /* - Rust contract demo runner + Rust contract demo runner */ extern crate parity_wasm; extern crate wasm_utils; -mod alloc; -mod storage; mod call_args; mod runtime; -mod gas_counter; use std::env; +use std::sync::Arc; use parity_wasm::interpreter::{self, ModuleInstanceInterface}; use parity_wasm::elements; @@ -21,73 +19,81 @@ pub const DEFAULT_MEMORY_INDEX: interpreter::ItemIndex = interpreter::ItemIndex: pub type WasmMemoryPtr = i32; fn main() { - // First, load wasm contract as a module - wasm_utils::init_log(); + // First, load wasm contract as a module + wasm_utils::init_log(); - let args = env::args().collect::>(); - if args.len() != 2 { - println!("Usage: {} contract.wasm", args[0]); - return; - } + let args = env::args().collect::>(); + if args.len() != 2 { + println!("Usage: {} contract.wasm", args[0]); + return; + } - let module = parity_wasm::deserialize_file(&args[1]).expect("Module deserialization to succeed"); + let module = parity_wasm::deserialize_file(&args[1]).expect("Module deserialization to succeed"); - // Second, create runtime and program instance - let runtime = runtime::Runtime::with_params( - 5*1024*1024, // default stack space - 65536, // runner arbitrary gas limit - ); + let program = parity_wasm::interpreter::ProgramInstance::new() + .expect("Program instance to be created"); - let mut user_functions = interpreter::UserFunctions::new(); - user_functions.insert("gas".to_owned(), - interpreter::UserFunction { - params: vec![elements::ValueType::I32], - result: None, - closure: Box::new(runtime.gas_counter()), - } - ); - user_functions.insert("_malloc".to_owned(), - interpreter::UserFunction { - params: vec![elements::ValueType::I32], - result: Some(elements::ValueType::I32), - closure: Box::new(runtime.allocator()), - } - ); - user_functions.insert("_storage_read".to_owned(), - interpreter::UserFunction { - params: vec![elements::ValueType::I32, elements::ValueType::I32], - result: Some(elements::ValueType::I32), - closure: Box::new(runtime.storage().reader()), - } - ); - user_functions.insert("_storage_write".to_owned(), - interpreter::UserFunction { - params: vec![elements::ValueType::I32, elements::ValueType::I32], - result: Some(elements::ValueType::I32), - closure: Box::new(runtime.storage().writer()), - } - ); - runtime::user_trap(&mut user_functions, "_emscripten_memcpy_big"); - runtime::user_trap(&mut user_functions, "invoke_vii"); - runtime::user_noop(&mut user_functions, "_free"); + // Add module to the programm + let module_instance = program.add_module("contract", module).expect("Module to be added successfully"); - let program = parity_wasm::interpreter::ProgramInstance::with_functions(user_functions) - .expect("Program instance to be created"); + { + let env_instance = program.module("env").expect("env module to exist"); + let env_memory = env_instance.memory(interpreter::ItemIndex::Internal(0)) + .expect("liner memory to exist"); - // Add module to the programm - let module_instance = program.add_module("contract", module).expect("Module to be added successfully"); + // Second, create runtime and program instance + let mut runtime = runtime::Runtime::with_params( + env_memory.clone(), // memory shared ptr + 5*1024*1024, // default stack space + 65536, // runner arbitrary gas limit + ); - // Initialize call descriptor - let descriptor = call_args::init( - &*program.module("env").expect("env module to exist"), - &runtime, - &[3u8; 128], - ).expect("call descriptor initialization to succeed"); + // Initialize call descriptor + let descriptor = call_args::init( + &*env_memory, + &mut runtime, + &[3u8; 128], + ).expect("call descriptor initialization to succeed"); - // Invoke _call method of the module - let return_ptr = module_instance.execute_export("_call", vec![descriptor.into()]) - .expect("_call to execute successfully") - .expect("_call function to return result ptr"); + // create native env module with native add && sub implementations + let functions = interpreter::UserFunctions { + executor: &mut runtime, + functions: vec![ + interpreter::UserFunction { + name: "_storage_read".to_owned(), + params: vec![elements::ValueType::I32, elements::ValueType::I32], + result: Some(elements::ValueType::I32), + }, + interpreter::UserFunction { + name: "_storage_write".to_owned(), + params: vec![elements::ValueType::I32, elements::ValueType::I32], + result: Some(elements::ValueType::I32), + }, + interpreter::UserFunction { + name: "_malloc".to_owned(), + params: vec![elements::ValueType::I32], + result: Some(elements::ValueType::I32), + }, + interpreter::UserFunction { + name: "gas".to_owned(), + params: vec![elements::ValueType::I32], + result: None, + }, + interpreter::UserFunction { + name: "_free".to_owned(), + params: vec![elements::ValueType::I32], + result: None, + }, + ], + }; + let native_env_instance = Arc::new(interpreter::env_native_module(env_instance, functions).unwrap()); - // ??? + // Form ExecutionParams (payload + env link) + let params = interpreter::ExecutionParams::with_external("env".into(), native_env_instance) + .add_argument(interpreter::RuntimeValue::I32(descriptor)); + + module_instance.execute_export("_call", params) + .expect("_call to execute successfully") + .expect("_call function to return result ptr"); + } } \ No newline at end of file diff --git a/rust-runner/src/runtime.rs b/rust-runner/src/runtime.rs index fcc8989..4cdc84b 100644 --- a/rust-runner/src/runtime.rs +++ b/rust-runner/src/runtime.rs @@ -1,92 +1,165 @@ use std::sync::Arc; -use std::cell::{Cell, RefCell}; use std::collections::HashMap; -use parity_wasm::{interpreter, elements}; -use {alloc, gas_counter, storage}; +use parity_wasm::interpreter; -#[derive(Default)] -pub struct RuntimeEnv { - pub gas_counter: Cell, - pub gas_limit: u64, - pub dynamic_top: Cell, - pub storage: RefCell>, +#[derive(Hash, PartialEq, Eq, Debug)] +pub struct StorageKey([u8; 32]); + +#[derive(Debug, Default)] +pub struct StorageValue([u8; 32]); + +struct ErrorStorage; + +impl StorageKey { + // todo: deal with memory views + fn from_mem(vec: Vec) -> Result { + if vec.len() != 32 { return Err(ErrorStorage); } + let mut result = StorageKey([0u8; 32]); + result.0.copy_from_slice(&vec[0..32]); + Ok(result) + } } -#[derive(Default, Clone)] -pub struct Runtime(Arc); +impl StorageValue { + // todo: deal with memory views + // todo: deal with variable-length values when it comes + fn from_mem(vec: Vec) -> Result { + if vec.len() != 32 { return Err(ErrorStorage); } + let mut result = StorageValue([0u8; 32]); + result.0.copy_from_slice(&vec[0..32]); + Ok(result) + } + + fn as_slice(&self) -> &[u8] { + &self.0 + } +} + +pub struct Runtime { + gas_counter: u64, + gas_limit: u64, + dynamic_top: u32, + storage: HashMap, + memory: Arc, +} + +#[derive(Debug)] +pub struct ErrorAlloc; impl Runtime { - pub fn with_params(stack_space: u32, gas_limit: u64) -> Runtime { - Runtime(Arc::new(RuntimeEnv { - gas_counter: Cell::new(0), - gas_limit: gas_limit, - dynamic_top: Cell::new(stack_space), - storage: RefCell::new(HashMap::new()), - })) - } + pub fn with_params(memory: Arc, stack_space: u32, gas_limit: u64) -> Runtime { + Runtime { + gas_counter: 0, + gas_limit: gas_limit, + dynamic_top: stack_space, + storage: HashMap::new(), + memory: memory, + } + } - pub fn allocator(&self) -> alloc::Arena { - alloc::Arena { - runtime: self.clone(), - } - } + pub fn storage_write(&mut self, context: interpreter::CallerContext) + -> Result, interpreter::Error> + { + let val_ptr = context.value_stack.pop_as::()?; + let key_ptr = context.value_stack.pop_as::()?; - pub fn gas_counter(&self) -> gas_counter::GasCounter { - gas_counter::GasCounter { - runtime: self.clone(), - } - } + let key = StorageKey::from_mem(self.memory.get(key_ptr as u32, 32)?) + .map_err(|_| interpreter::Error::Trap("Memory access violation".to_owned()))?; + let val = StorageValue::from_mem(self.memory.get(val_ptr as u32, 32)?) + .map_err(|_| interpreter::Error::Trap("Memory access violation".to_owned()))?; - pub fn storage(&self) -> storage::Storage { - storage::Storage::new(self.clone()) - } + println!("write storage {:?} = {:?}", key, val); - pub fn env(&self) -> &RuntimeEnv { - &*self.0 - } + self.storage.insert(key, val); + + Ok(Some(0i32.into())) + } + + pub fn storage_read(&mut self, context: interpreter::CallerContext) + -> Result, interpreter::Error> + { + // arguments passed are in backward order (since it is stack) + let val_ptr = context.value_stack.pop_as::()?; + let key_ptr = context.value_stack.pop_as::()?; + + let key = StorageKey::from_mem(self.memory.get(key_ptr as u32, 32)?) + .map_err(|_| interpreter::Error::Trap("Memory access violation".to_owned()))?; + let empty = StorageValue([0u8; 32]); + let val = self.storage.get(&key).unwrap_or(&empty); + + self.memory.set(val_ptr as u32, val.as_slice())?; + + println!("read storage {:?} (evaluated as {:?})", key, val); + + Ok(Some(0.into())) + } + + pub fn malloc(&mut self, context: interpreter::CallerContext) + -> Result, interpreter::Error> + { + let amount = context.value_stack.pop_as::()? as u32; + let previous_top = self.dynamic_top; + self.dynamic_top = previous_top + amount; + Ok(Some((previous_top as i32).into())) + } + + pub fn alloc(&mut self, amount: u32) -> Result { + let previous_top = self.dynamic_top; + self.dynamic_top = previous_top + amount; + Ok(previous_top.into()) + } + + fn gas(&mut self, context: interpreter::CallerContext) + -> Result, interpreter::Error> + { + let prev = self.gas_counter; + let update = context.value_stack.pop_as::()? as u64; + if prev + update > self.gas_limit { + // exceeds gas + Err(interpreter::Error::Trap(format!("Gas exceeds limits of {}", self.gas_limit))) + } else { + self.gas_counter = prev + update; + Ok(None) + } + } + + fn user_trap(&mut self, _context: interpreter::CallerContext) + -> Result, interpreter::Error> + { + Err(interpreter::Error::Trap("unknown trap".to_owned())) + } + + fn user_noop(&mut self, + _context: interpreter::CallerContext + ) -> Result, interpreter::Error> { + Ok(None) + } } -pub fn user_trap(funcs: &mut interpreter::UserFunctions, func_name: &str) { - let func_str = func_name.to_owned(); - funcs.insert(func_str.clone(), - interpreter::UserFunction { - params: vec![], - result: Some(elements::ValueType::I32), - closure: Box::new(UserTrap(func_str)), - } - ); -} - -struct UserTrap(String); - -impl interpreter::UserFunctionInterface for UserTrap { - fn call(&mut self, - _module: &interpreter::ModuleInstance, - _context: interpreter::CallerContext - ) -> Result, interpreter::Error> { - Err(interpreter::Error::Trap(self.0.clone())) - } -} - -struct UserNoop; - -pub fn user_noop(funcs: &mut interpreter::UserFunctions, func_name: &str) { - let func_str = func_name.to_owned(); - funcs.insert(func_str.clone(), - interpreter::UserFunction { - params: vec![], - result: None, - closure: Box::new(UserNoop), - } - ); -} - -impl interpreter::UserFunctionInterface for UserNoop { - fn call(&mut self, - _module: &interpreter::ModuleInstance, - _context: interpreter::CallerContext - ) -> Result, interpreter::Error> { - Ok(None) - } +impl interpreter::UserFunctionExecutor for Runtime { + fn execute(&mut self, name: &str, context: interpreter::CallerContext) + -> Result, interpreter::Error> + { + match name { + "_malloc" => { + self.malloc(context) + }, + "_free" => { + self.user_noop(context) + }, + "_storage_read" => { + self.storage_read(context) + }, + "_storage_write" => { + self.storage_write(context) + }, + "gas" => { + self.gas(context) + }, + _ => { + self.user_trap(context) + } + } + } } \ No newline at end of file diff --git a/rust-runner/src/storage.rs b/rust-runner/src/storage.rs deleted file mode 100644 index 246d688..0000000 --- a/rust-runner/src/storage.rs +++ /dev/null @@ -1,118 +0,0 @@ -use parity_wasm::interpreter::{self, ItemIndex, ModuleInstanceInterface}; -use std::sync::Arc; - -use DEFAULT_MEMORY_INDEX; -use runtime::Runtime; - -#[derive(Hash, PartialEq, Eq, Debug)] -pub struct StorageKey([u8; 32]); - -#[derive(Debug, Default)] -pub struct StorageValue([u8; 32]); - -impl StorageKey { - // todo: deal with memory views - fn from_mem(vec: Vec) -> Result { - if vec.len() != 32 { return Err(Error); } - let mut result = StorageKey([0u8; 32]); - result.0.copy_from_slice(&vec[0..32]); - Ok(result) - } -} - -impl StorageValue { - // todo: deal with memory views - // todo: deal with variable-length values when it comes - fn from_mem(vec: Vec) -> Result { - if vec.len() != 32 { return Err(Error); } - let mut result = StorageValue([0u8; 32]); - result.0.copy_from_slice(&vec[0..32]); - Ok(result) - } - - fn as_slice(&self) -> &[u8] { - &self.0 - } -} - -pub struct Storage { - runtime: Runtime, -} - -pub struct Error; - -impl Storage { - - pub fn new(runtime: Runtime) -> Self { - Storage { - runtime: runtime, - } - } - - pub fn writer(self) -> StorageWrite { - StorageWrite(self) - } - - pub fn reader(self) -> StorageRead { - StorageRead(self) - } -} - -pub struct StorageWrite(Storage); - -impl interpreter::UserFunctionInterface for StorageWrite { - fn call(&mut self, - module: &interpreter::ModuleInstance, - context: interpreter::CallerContext, - ) -> Result, interpreter::Error> { - - // arguments passed are in backward order (since it is stack) - let val_ptr = context.value_stack.pop_as::()?; - let key_ptr = context.value_stack.pop_as::()?; - - let memory = match module.memory(DEFAULT_MEMORY_INDEX) { - Err(_) => { return Ok(Some((-1i32).into())) }, - Ok(memory) => memory, - }; - - let key = StorageKey::from_mem(memory.get(key_ptr as u32, 32)?) - .map_err(|_| interpreter::Error::Trap("Memory access violation".to_owned()))?; - let val = StorageValue::from_mem(memory.get(val_ptr as u32, 32)?) - .map_err(|_| interpreter::Error::Trap("Memory access violation".to_owned()))?; - - println!("set storage {:?} = {:?}", key, val); - - Ok(Some(0.into())) - } -} - -pub struct StorageRead(Storage); - -impl interpreter::UserFunctionInterface for StorageRead { - fn call(&mut self, - module: &interpreter::ModuleInstance, - context: interpreter::CallerContext, - ) -> Result, interpreter::Error> { - - // arguments passed are in backward order (since it is stack) - let val_ptr = context.value_stack.pop_as::()?; - let key_ptr = context.value_stack.pop_as::()?; - - let memory = match module.memory(DEFAULT_MEMORY_INDEX) { - Err(_) => { return Ok(Some((-1i32).into())) }, - Ok(memory) => memory, - }; - - let key = StorageKey::from_mem(memory.get(key_ptr as u32, 32)?) - .map_err(|_| interpreter::Error::Trap("Memory access violation".to_owned()))?; - let empty = StorageValue([0u8; 32]); - let storage = self.0.runtime.env().storage.borrow(); - let val = storage.get(&key).unwrap_or(&empty); - - memory.set(val_ptr as u32, val.as_slice()); - - println!("read storage {:?} (evaluated as {:?})", key, val); - - Ok(Some(0.into())) - } -} \ No newline at end of file diff --git a/samples/empty_contract.rs b/samples/empty_contract.rs index 027a60c..f226f2b 100644 --- a/samples/empty_contract.rs +++ b/samples/empty_contract.rs @@ -13,15 +13,15 @@ pub fn call() { /* This produces the following code (after injecting gas counter & optimizing) (module - (type (;0;) (func)) - (type (;1;) (func (param i32))) - (import "env" "memory" (memory (;0;) 256 256)) - (import "env" "table" (table (;0;) 0 0 anyfunc)) - (import "env" "gas" (func (;0;) (type 1))) - (func (;1;) (type 0) - i32.const 2 - call 0 - nop) - (export "_call" (func 1)) - (data (i32.const 1212) "\1c\05")) + (type (;0;) (func)) + (type (;1;) (func (param i32))) + (import "env" "memory" (memory (;0;) 256 256)) + (import "env" "table" (table (;0;) 0 0 anyfunc)) + (import "env" "gas" (func (;0;) (type 1))) + (func (;1;) (type 0) + i32.const 2 + call 0 + nop) + (export "_call" (func 1)) + (data (i32.const 1212) "\1c\05")) */ \ No newline at end of file diff --git a/src/ext.rs b/src/ext.rs index 1cfd528..2571c58 100644 --- a/src/ext.rs +++ b/src/ext.rs @@ -3,99 +3,99 @@ use parity_wasm::{elements, builder}; type Insertion = (usize, u32, u32, String); pub fn update_call_index(opcodes: &mut elements::Opcodes, original_imports: usize, inserts: &[Insertion]) { - use parity_wasm::elements::Opcode::*; - for opcode in opcodes.elements_mut().iter_mut() { - match opcode { - &mut Block(_, ref mut block) | &mut If(_, ref mut block) | &mut Loop(_, ref mut block) => { - update_call_index(block, original_imports, inserts) - }, - &mut Call(ref mut call_index) => { - if let Some(pos) = inserts.iter().position(|x| x.1 == *call_index) { - *call_index = (original_imports + pos) as u32; - } else if *call_index as usize > original_imports { - *call_index += inserts.len() as u32; - } - }, - _ => { } - } - } + use parity_wasm::elements::Opcode::*; + for opcode in opcodes.elements_mut().iter_mut() { + match opcode { + &mut Block(_, ref mut block) | &mut If(_, ref mut block) | &mut Loop(_, ref mut block) => { + update_call_index(block, original_imports, inserts) + }, + &mut Call(ref mut call_index) => { + if let Some(pos) = inserts.iter().position(|x| x.1 == *call_index) { + *call_index = (original_imports + pos) as u32; + } else if *call_index as usize > original_imports { + *call_index += inserts.len() as u32; + } + }, + _ => { } + } + } } pub fn externalize( - module: elements::Module, - replaced_funcs: Vec<&str>, + module: elements::Module, + replaced_funcs: Vec<&str>, ) -> elements::Module { // Save import functions number for later - let import_funcs_total = module - .import_section().expect("Import section to exist") - .entries() - .iter() - .filter(|e| if let &elements::External::Function(_) = e.external() { true } else { false }) - .count(); + let import_funcs_total = module + .import_section().expect("Import section to exist") + .entries() + .iter() + .filter(|e| if let &elements::External::Function(_) = e.external() { true } else { false }) + .count(); - // First, we find functions indices that are to be rewired to externals - // Triple is (function_index (callable), type_index, function_name) - let mut replaces: Vec = replaced_funcs - .into_iter() - .filter_map(|f| { - let export = module - .export_section().expect("Export section to exist") - .entries().iter().enumerate() - .find(|&(_, entry)| entry.field() == f) - .expect("All functions of interest to exist"); + // First, we find functions indices that are to be rewired to externals + // Triple is (function_index (callable), type_index, function_name) + let mut replaces: Vec = replaced_funcs + .into_iter() + .filter_map(|f| { + let export = module + .export_section().expect("Export section to exist") + .entries().iter().enumerate() + .find(|&(_, entry)| entry.field() == f) + .expect("All functions of interest to exist"); - if let &elements::Internal::Function(func_idx) = export.1.internal() { - let type_ref = module - .functions_section().expect("Functions section to exist") - .entries()[func_idx as usize - import_funcs_total] - .type_ref(); + if let &elements::Internal::Function(func_idx) = export.1.internal() { + let type_ref = module + .functions_section().expect("Functions section to exist") + .entries()[func_idx as usize - import_funcs_total] + .type_ref(); - Some((export.0, func_idx, type_ref, export.1.field().to_owned())) - } else { - None - } - }) - .collect(); + Some((export.0, func_idx, type_ref, export.1.field().to_owned())) + } else { + None + } + }) + .collect(); - replaces.sort_by_key(|e| e.0); + replaces.sort_by_key(|e| e.0); - // Second, we duplicate them as import definitions - let mut mbuilder = builder::from_module(module); - for &(_, _, type_ref, ref field) in replaces.iter() { - mbuilder.push_import( - builder::import() - .module("env") - .field(field) - .external().func(type_ref) - .build() - ); - } + // Second, we duplicate them as import definitions + let mut mbuilder = builder::from_module(module); + for &(_, _, type_ref, ref field) in replaces.iter() { + mbuilder.push_import( + builder::import() + .module("env") + .field(field) + .external().func(type_ref) + .build() + ); + } - // Back to mutable access - let mut module = mbuilder.build(); + // Back to mutable access + let mut module = mbuilder.build(); - // Third, rewire all calls to imported functions and update all other calls indices - for section in module.sections_mut() { - match section { - &mut elements::Section::Code(ref mut code_section) => { - for ref mut func_body in code_section.bodies_mut() { - update_call_index(func_body.code_mut(), import_funcs_total, &replaces); - } - }, - &mut elements::Section::Export(ref mut export_section) => { - for ref mut export in export_section.entries_mut() { - match export.internal_mut() { - &mut elements::Internal::Function(ref mut func_index) => { - if *func_index >= import_funcs_total as u32 { *func_index += replaces.len() as u32; } - }, - _ => {} - } - } - }, - _ => { } - } - } + // Third, rewire all calls to imported functions and update all other calls indices + for section in module.sections_mut() { + match section { + &mut elements::Section::Code(ref mut code_section) => { + for ref mut func_body in code_section.bodies_mut() { + update_call_index(func_body.code_mut(), import_funcs_total, &replaces); + } + }, + &mut elements::Section::Export(ref mut export_section) => { + for ref mut export in export_section.entries_mut() { + match export.internal_mut() { + &mut elements::Internal::Function(ref mut func_index) => { + if *func_index >= import_funcs_total as u32 { *func_index += replaces.len() as u32; } + }, + _ => {} + } + } + }, + _ => { } + } + } - module + module } \ No newline at end of file diff --git a/src/gas.rs b/src/gas.rs index e69c288..a57e9d6 100644 --- a/src/gas.rs +++ b/src/gas.rs @@ -2,98 +2,98 @@ use parity_wasm::{elements, builder}; pub fn update_call_index(opcodes: &mut elements::Opcodes, inserted_index: u32) { - use parity_wasm::elements::Opcode::*; - for opcode in opcodes.elements_mut().iter_mut() { - match opcode { - &mut Block(_, ref mut block) | &mut If(_, ref mut block) | &mut Loop(_, ref mut block) => { - update_call_index(block, inserted_index) - }, - &mut Call(ref mut call_index) => { - if *call_index >= inserted_index { *call_index += 1} - }, - _ => { } - } - } + use parity_wasm::elements::Opcode::*; + for opcode in opcodes.elements_mut().iter_mut() { + match opcode { + &mut Block(_, ref mut block) | &mut If(_, ref mut block) | &mut Loop(_, ref mut block) => { + update_call_index(block, inserted_index) + }, + &mut Call(ref mut call_index) => { + if *call_index >= inserted_index { *call_index += 1} + }, + _ => { } + } + } } pub fn inject_counter(opcodes: &mut elements::Opcodes, gas_func: u32) { - use parity_wasm::elements::Opcode::*; - for opcode in opcodes.elements_mut().iter_mut() { - match opcode { - &mut Block(_, ref mut block) | &mut If(_, ref mut block) | &mut Loop(_, ref mut block) => { - inject_counter(block, gas_func) - }, - _ => { } - } - } + use parity_wasm::elements::Opcode::*; + for opcode in opcodes.elements_mut().iter_mut() { + match opcode { + &mut Block(_, ref mut block) | &mut If(_, ref mut block) | &mut Loop(_, ref mut block) => { + inject_counter(block, gas_func) + }, + _ => { } + } + } - let ops = opcodes.elements_mut().len() as u32; - opcodes.elements_mut().insert(0, I32Const(ops as i32)); - opcodes.elements_mut().insert(1, Call(gas_func)); + let ops = opcodes.elements_mut().len() as u32; + opcodes.elements_mut().insert(0, I32Const(ops as i32)); + opcodes.elements_mut().insert(1, Call(gas_func)); } pub fn inject_gas_counter(module: elements::Module) -> elements::Module { - // Injecting gas counting external - let mut mbuilder = builder::from_module(module); - let import_sig = mbuilder.push_signature( - builder::signature() - .param().i32() - .build_sig() - ); + // Injecting gas counting external + let mut mbuilder = builder::from_module(module); + let import_sig = mbuilder.push_signature( + builder::signature() + .param().i32() + .build_sig() + ); - let mut gas_func = mbuilder.push_import( - builder::import() - .module("env") - .field("gas") - .external().func(import_sig) - .build() - ); + let mut gas_func = mbuilder.push_import( + builder::import() + .module("env") + .field("gas") + .external().func(import_sig) + .build() + ); - // back to plain module - let mut module = mbuilder.build(); + // back to plain module + let mut module = mbuilder.build(); - assert!(module.global_section().is_some()); + assert!(module.global_section().is_some()); - // calculate actual function index of the imported definition - // (substract all imports that are NOT functions) + // calculate actual function index of the imported definition + // (substract all imports that are NOT functions) - for import_entry in module.import_section().expect("Builder should have insert the import section").entries() { - match *import_entry.external() { - elements::External::Function(_) => {}, - _ => { gas_func -= 1; } - } - } + for import_entry in module.import_section().expect("Builder should have insert the import section").entries() { + match *import_entry.external() { + elements::External::Function(_) => {}, + _ => { gas_func -= 1; } + } + } - // Updating calling addresses (all calls to function index >= `gas_func` should be incremented) - for section in module.sections_mut() { - match section { - &mut elements::Section::Code(ref mut code_section) => { - for ref mut func_body in code_section.bodies_mut() { - update_call_index(func_body.code_mut(), gas_func); - inject_counter(func_body.code_mut(), gas_func); - } - }, - &mut elements::Section::Export(ref mut export_section) => { - for ref mut export in export_section.entries_mut() { - match export.internal_mut() { - &mut elements::Internal::Function(ref mut func_index) => { - if *func_index >= gas_func { *func_index += 1} - }, - _ => {} - } - } - }, - &mut elements::Section::Element(ref mut elements_section) => { - for ref mut segment in elements_section.entries_mut() { - // update all indirect call addresses initial values - for func_index in segment.members_mut() { - if *func_index >= gas_func { *func_index += 1} - } - } - }, - _ => { } - } - } + // Updating calling addresses (all calls to function index >= `gas_func` should be incremented) + for section in module.sections_mut() { + match section { + &mut elements::Section::Code(ref mut code_section) => { + for ref mut func_body in code_section.bodies_mut() { + update_call_index(func_body.code_mut(), gas_func); + inject_counter(func_body.code_mut(), gas_func); + } + }, + &mut elements::Section::Export(ref mut export_section) => { + for ref mut export in export_section.entries_mut() { + match export.internal_mut() { + &mut elements::Internal::Function(ref mut func_index) => { + if *func_index >= gas_func { *func_index += 1} + }, + _ => {} + } + } + }, + &mut elements::Section::Element(ref mut elements_section) => { + for ref mut segment in elements_section.entries_mut() { + // update all indirect call addresses initial values + for func_index in segment.members_mut() { + if *func_index >= gas_func { *func_index += 1} + } + } + }, + _ => { } + } + } - module + module } \ No newline at end of file diff --git a/src/optimizer.rs b/src/optimizer.rs index 2b5aa63..2053b0d 100644 --- a/src/optimizer.rs +++ b/src/optimizer.rs @@ -5,546 +5,546 @@ use symbols::{Symbol, expand_symbols, push_code_symbols, resolve_function}; #[derive(Debug)] pub enum Error { - /// Since optimizer starts with export entries, export - /// section is supposed to exist. - NoExportSection, + /// Since optimizer starts with export entries, export + /// section is supposed to exist. + NoExportSection, } pub fn optimize( - module: &mut elements::Module, // Module to optimize - used_exports: Vec<&str>, // List of only exports that will be usable after optimization + module: &mut elements::Module, // Module to optimize + used_exports: Vec<&str>, // List of only exports that will be usable after optimization ) -> Result<(), Error> { - // WebAssembly exports optimizer - // Motivation: emscripten compiler backend compiles in many unused exports - // which in turn compile in unused imports and leaves unused functions + // WebAssembly exports optimizer + // Motivation: emscripten compiler backend compiles in many unused exports + // which in turn compile in unused imports and leaves unused functions - // Algo starts from the top, listing all items that should stay - let mut stay = HashSet::new(); - for (index, entry) in module.export_section().ok_or(Error::NoExportSection)?.entries().iter().enumerate() { - if used_exports.iter().find(|e| **e == entry.field()).is_some() { - stay.insert(Symbol::Export(index)); - } - } + // Algo starts from the top, listing all items that should stay + let mut stay = HashSet::new(); + for (index, entry) in module.export_section().ok_or(Error::NoExportSection)?.entries().iter().enumerate() { + if used_exports.iter().find(|e| **e == entry.field()).is_some() { + stay.insert(Symbol::Export(index)); + } + } - // All symbols used in data/element segments are also should be preserved - let mut init_symbols = Vec::new(); - if let Some(data_section) = module.data_section() { - for segment in data_section.entries() { - push_code_symbols(&module, segment.offset().code(), &mut init_symbols); - } - } - if let Some(elements_section) = module.elements_section() { - for segment in elements_section.entries() { - push_code_symbols(&module, segment.offset().code(), &mut init_symbols); - for func_index in segment.members() { - stay.insert(resolve_function(&module, *func_index)); - } - } - } - for symbol in init_symbols.drain(..) { stay.insert(symbol); } + // All symbols used in data/element segments are also should be preserved + let mut init_symbols = Vec::new(); + if let Some(data_section) = module.data_section() { + for segment in data_section.entries() { + push_code_symbols(&module, segment.offset().code(), &mut init_symbols); + } + } + if let Some(elements_section) = module.elements_section() { + for segment in elements_section.entries() { + push_code_symbols(&module, segment.offset().code(), &mut init_symbols); + for func_index in segment.members() { + stay.insert(resolve_function(&module, *func_index)); + } + } + } + for symbol in init_symbols.drain(..) { stay.insert(symbol); } - // Call function which will traverse the list recursively, filling stay with all symbols - // that are already used by those which already there - expand_symbols(module, &mut stay); + // Call function which will traverse the list recursively, filling stay with all symbols + // that are already used by those which already there + expand_symbols(module, &mut stay); - for symbol in stay.iter() { - trace!("symbol to stay: {:?}", symbol); - } + for symbol in stay.iter() { + trace!("symbol to stay: {:?}", symbol); + } - // Keep track of referreable symbols to rewire calls/globals - let mut eliminated_funcs = Vec::new(); - let mut eliminated_globals = Vec::new(); - let mut eliminated_types = Vec::new(); + // Keep track of referreable symbols to rewire calls/globals + let mut eliminated_funcs = Vec::new(); + let mut eliminated_globals = Vec::new(); + let mut eliminated_types = Vec::new(); - // First, iterate through types - let mut index = 0; - let mut old_index = 0; + // First, iterate through types + let mut index = 0; + let mut old_index = 0; - { - loop { - if type_section(module).map(|section| section.types_mut().len()).unwrap_or(0) == index { break; } + { + loop { + if type_section(module).map(|section| section.types_mut().len()).unwrap_or(0) == index { break; } - if stay.contains(&Symbol::Type(old_index)) { - index += 1; - } else { - type_section(module) - .expect("If type section does not exists, the loop will break at the beginning of first iteration") - .types_mut().remove(index); - eliminated_types.push(old_index); - trace!("Eliminated type({})", old_index); - } - old_index += 1; - } - } + if stay.contains(&Symbol::Type(old_index)) { + index += 1; + } else { + type_section(module) + .expect("If type section does not exists, the loop will break at the beginning of first iteration") + .types_mut().remove(index); + eliminated_types.push(old_index); + trace!("Eliminated type({})", old_index); + } + old_index += 1; + } + } - // Second, iterate through imports - let mut top_funcs = 0; - let mut top_globals = 0; - index = 0; - old_index = 0; + // Second, iterate through imports + let mut top_funcs = 0; + let mut top_globals = 0; + index = 0; + old_index = 0; - if let Some(imports) = import_section(module) { - loop { - let mut remove = false; - match imports.entries()[index].external() { - &elements::External::Function(_) => { - if stay.contains(&Symbol::Import(old_index)) { - index += 1; - } else { - remove = true; - eliminated_funcs.push(top_funcs); - trace!("Eliminated import({}) func({}, {})", old_index, top_funcs, imports.entries()[index].field()); - } - top_funcs += 1; - }, - &elements::External::Global(_) => { - if stay.contains(&Symbol::Import(old_index)) { - index += 1; - } else { - remove = true; - eliminated_globals.push(top_globals); - trace!("Eliminated import({}) global({}, {})", old_index, top_globals, imports.entries()[index].field()); - } - top_globals += 1; - }, - _ => { - index += 1; - } - } - if remove { - imports.entries_mut().remove(index); - } + if let Some(imports) = import_section(module) { + loop { + let mut remove = false; + match imports.entries()[index].external() { + &elements::External::Function(_) => { + if stay.contains(&Symbol::Import(old_index)) { + index += 1; + } else { + remove = true; + eliminated_funcs.push(top_funcs); + trace!("Eliminated import({}) func({}, {})", old_index, top_funcs, imports.entries()[index].field()); + } + top_funcs += 1; + }, + &elements::External::Global(_) => { + if stay.contains(&Symbol::Import(old_index)) { + index += 1; + } else { + remove = true; + eliminated_globals.push(top_globals); + trace!("Eliminated import({}) global({}, {})", old_index, top_globals, imports.entries()[index].field()); + } + top_globals += 1; + }, + _ => { + index += 1; + } + } + if remove { + imports.entries_mut().remove(index); + } - old_index += 1; + old_index += 1; - if index == imports.entries().len() { break; } - } - } + if index == imports.entries().len() { break; } + } + } - // Third, iterate through globals - if let Some(globals) = global_section(module) { - index = 0; - old_index = 0; + // Third, iterate through globals + if let Some(globals) = global_section(module) { + index = 0; + old_index = 0; - loop { - if globals.entries_mut().len() == index { break; } - if stay.contains(&Symbol::Global(old_index)) { - index += 1; - } else { - globals.entries_mut().remove(index); - eliminated_globals.push(top_globals + old_index); - trace!("Eliminated global({})", top_globals + old_index); - } - old_index += 1; - } - } + loop { + if globals.entries_mut().len() == index { break; } + if stay.contains(&Symbol::Global(old_index)) { + index += 1; + } else { + globals.entries_mut().remove(index); + eliminated_globals.push(top_globals + old_index); + trace!("Eliminated global({})", top_globals + old_index); + } + old_index += 1; + } + } - // Forth, delete orphaned functions - if functions_section(module).is_some() && code_section(module).is_some() { - index = 0; - old_index = 0; + // Forth, delete orphaned functions + if functions_section(module).is_some() && code_section(module).is_some() { + index = 0; + old_index = 0; - loop { - if functions_section(module).expect("Functons section to exist").entries_mut().len() == index { break; } - if stay.contains(&Symbol::Function(old_index)) { - index += 1; - } else { - functions_section(module).expect("Functons section to exist").entries_mut().remove(index); - code_section(module).expect("Code section to exist").bodies_mut().remove(index); + loop { + if functions_section(module).expect("Functons section to exist").entries_mut().len() == index { break; } + if stay.contains(&Symbol::Function(old_index)) { + index += 1; + } else { + functions_section(module).expect("Functons section to exist").entries_mut().remove(index); + code_section(module).expect("Code section to exist").bodies_mut().remove(index); - eliminated_funcs.push(top_funcs + old_index); - trace!("Eliminated function({})", top_funcs + old_index); - } - old_index += 1; - } - } + eliminated_funcs.push(top_funcs + old_index); + trace!("Eliminated function({})", top_funcs + old_index); + } + old_index += 1; + } + } - // Fifth, eliminate unused exports - { - let exports = export_section(module).ok_or(Error::NoExportSection)?; + // Fifth, eliminate unused exports + { + let exports = export_section(module).ok_or(Error::NoExportSection)?; - index = 0; - old_index = 0; + index = 0; + old_index = 0; - loop { - if exports.entries_mut().len() == index { break; } - if stay.contains(&Symbol::Export(old_index)) { - index += 1; - } else { - trace!("Eliminated export({}, {})", old_index, exports.entries_mut()[index].field()); - exports.entries_mut().remove(index); - } - old_index += 1; - } - } + loop { + if exports.entries_mut().len() == index { break; } + if stay.contains(&Symbol::Export(old_index)) { + index += 1; + } else { + trace!("Eliminated export({}, {})", old_index, exports.entries_mut()[index].field()); + exports.entries_mut().remove(index); + } + old_index += 1; + } + } - if eliminated_globals.len() > 0 || eliminated_funcs.len() > 0 || eliminated_types.len() > 0 { - // Finaly, rewire all calls, globals references and types to the new indices - // (only if there is anything to do) - eliminated_globals.sort(); - eliminated_funcs.sort(); - eliminated_types.sort(); + if eliminated_globals.len() > 0 || eliminated_funcs.len() > 0 || eliminated_types.len() > 0 { + // Finaly, rewire all calls, globals references and types to the new indices + // (only if there is anything to do) + eliminated_globals.sort(); + eliminated_funcs.sort(); + eliminated_types.sort(); - for section in module.sections_mut() { - match section { - &mut elements::Section::Function(ref mut function_section) if eliminated_types.len() > 0 => { - for ref mut func_signature in function_section.entries_mut() { - let totalle = eliminated_types.iter().take_while(|i| (**i as u32) < func_signature.type_ref()).count(); - *func_signature.type_ref_mut() -= totalle as u32; - } - }, - &mut elements::Section::Import(ref mut import_section) if eliminated_types.len() > 0 => { - for ref mut import_entry in import_section.entries_mut() { - if let &mut elements::External::Function(ref mut type_ref) = import_entry.external_mut() { - let totalle = eliminated_types.iter().take_while(|i| (**i as u32) < *type_ref).count(); - *type_ref -= totalle as u32; - } - } - }, - &mut elements::Section::Code(ref mut code_section) if eliminated_globals.len() > 0 || eliminated_funcs.len() > 0 => { - for ref mut func_body in code_section.bodies_mut() { - if eliminated_funcs.len() > 0 { - update_call_index(func_body.code_mut(), &eliminated_funcs); - } - if eliminated_globals.len() > 0 { - update_global_index(func_body.code_mut().elements_mut(), &eliminated_globals) - } - } - }, - &mut elements::Section::Export(ref mut export_section) => { - for ref mut export in export_section.entries_mut() { - match export.internal_mut() { - &mut elements::Internal::Function(ref mut func_index) => { - let totalle = eliminated_funcs.iter().take_while(|i| (**i as u32) < *func_index).count(); - *func_index -= totalle as u32; - }, - &mut elements::Internal::Global(ref mut global_index) => { - let totalle = eliminated_globals.iter().take_while(|i| (**i as u32) < *global_index).count(); - *global_index -= totalle as u32; - }, - _ => {} - } - } - }, - &mut elements::Section::Global(ref mut global_section) => { - for ref mut global_entry in global_section.entries_mut() { - update_global_index(global_entry.init_expr_mut().code_mut(), &eliminated_globals) - } - }, - &mut elements::Section::Data(ref mut data_section) => { - for ref mut segment in data_section.entries_mut() { - update_global_index(segment.offset_mut().code_mut(), &eliminated_globals) - } - }, - &mut elements::Section::Element(ref mut elements_section) => { - for ref mut segment in elements_section.entries_mut() { - update_global_index(segment.offset_mut().code_mut(), &eliminated_globals); - // update all indirect call addresses initial values - for func_index in segment.members_mut() { - let totalle = eliminated_funcs.iter().take_while(|i| (**i as u32) < *func_index).count(); - *func_index -= totalle as u32; - } - } - }, - _ => { } - } - } - } + for section in module.sections_mut() { + match section { + &mut elements::Section::Function(ref mut function_section) if eliminated_types.len() > 0 => { + for ref mut func_signature in function_section.entries_mut() { + let totalle = eliminated_types.iter().take_while(|i| (**i as u32) < func_signature.type_ref()).count(); + *func_signature.type_ref_mut() -= totalle as u32; + } + }, + &mut elements::Section::Import(ref mut import_section) if eliminated_types.len() > 0 => { + for ref mut import_entry in import_section.entries_mut() { + if let &mut elements::External::Function(ref mut type_ref) = import_entry.external_mut() { + let totalle = eliminated_types.iter().take_while(|i| (**i as u32) < *type_ref).count(); + *type_ref -= totalle as u32; + } + } + }, + &mut elements::Section::Code(ref mut code_section) if eliminated_globals.len() > 0 || eliminated_funcs.len() > 0 => { + for ref mut func_body in code_section.bodies_mut() { + if eliminated_funcs.len() > 0 { + update_call_index(func_body.code_mut(), &eliminated_funcs); + } + if eliminated_globals.len() > 0 { + update_global_index(func_body.code_mut().elements_mut(), &eliminated_globals) + } + } + }, + &mut elements::Section::Export(ref mut export_section) => { + for ref mut export in export_section.entries_mut() { + match export.internal_mut() { + &mut elements::Internal::Function(ref mut func_index) => { + let totalle = eliminated_funcs.iter().take_while(|i| (**i as u32) < *func_index).count(); + *func_index -= totalle as u32; + }, + &mut elements::Internal::Global(ref mut global_index) => { + let totalle = eliminated_globals.iter().take_while(|i| (**i as u32) < *global_index).count(); + *global_index -= totalle as u32; + }, + _ => {} + } + } + }, + &mut elements::Section::Global(ref mut global_section) => { + for ref mut global_entry in global_section.entries_mut() { + update_global_index(global_entry.init_expr_mut().code_mut(), &eliminated_globals) + } + }, + &mut elements::Section::Data(ref mut data_section) => { + for ref mut segment in data_section.entries_mut() { + update_global_index(segment.offset_mut().code_mut(), &eliminated_globals) + } + }, + &mut elements::Section::Element(ref mut elements_section) => { + for ref mut segment in elements_section.entries_mut() { + update_global_index(segment.offset_mut().code_mut(), &eliminated_globals); + // update all indirect call addresses initial values + for func_index in segment.members_mut() { + let totalle = eliminated_funcs.iter().take_while(|i| (**i as u32) < *func_index).count(); + *func_index -= totalle as u32; + } + } + }, + _ => { } + } + } + } - Ok(()) + Ok(()) } pub fn update_call_index(opcodes: &mut elements::Opcodes, eliminated_indices: &[usize]) { - use parity_wasm::elements::Opcode::*; - for opcode in opcodes.elements_mut().iter_mut() { - match opcode { - &mut Block(_, ref mut block) | &mut If(_, ref mut block) | &mut Loop(_, ref mut block) => { - update_call_index(block, eliminated_indices) - }, - &mut Call(ref mut call_index) => { - let totalle = eliminated_indices.iter().take_while(|i| (**i as u32) < *call_index).count(); - trace!("rewired call {} -> call {}", *call_index, *call_index - totalle as u32); - *call_index -= totalle as u32; - }, - _ => { }, - } - } + use parity_wasm::elements::Opcode::*; + for opcode in opcodes.elements_mut().iter_mut() { + match opcode { + &mut Block(_, ref mut block) | &mut If(_, ref mut block) | &mut Loop(_, ref mut block) => { + update_call_index(block, eliminated_indices) + }, + &mut Call(ref mut call_index) => { + let totalle = eliminated_indices.iter().take_while(|i| (**i as u32) < *call_index).count(); + trace!("rewired call {} -> call {}", *call_index, *call_index - totalle as u32); + *call_index -= totalle as u32; + }, + _ => { }, + } + } } /// Updates global references considering the _ordered_ list of eliminated indices pub fn update_global_index(opcodes: &mut Vec, eliminated_indices: &[usize]) { - use parity_wasm::elements::Opcode::*; - for opcode in opcodes.iter_mut() { - match opcode { - &mut Block(_, ref mut block) | &mut If(_, ref mut block) | &mut Loop(_, ref mut block) => { - update_global_index(block.elements_mut(), eliminated_indices) - }, - &mut GetGlobal(ref mut index) | &mut SetGlobal(ref mut index) => { - let totalle = eliminated_indices.iter().take_while(|i| (**i as u32) < *index).count(); - trace!("rewired global {} -> global {}", *index, *index - totalle as u32); - *index -= totalle as u32; - }, - _ => { }, - } - } + use parity_wasm::elements::Opcode::*; + for opcode in opcodes.iter_mut() { + match opcode { + &mut Block(_, ref mut block) | &mut If(_, ref mut block) | &mut Loop(_, ref mut block) => { + update_global_index(block.elements_mut(), eliminated_indices) + }, + &mut GetGlobal(ref mut index) | &mut SetGlobal(ref mut index) => { + let totalle = eliminated_indices.iter().take_while(|i| (**i as u32) < *index).count(); + trace!("rewired global {} -> global {}", *index, *index - totalle as u32); + *index -= totalle as u32; + }, + _ => { }, + } + } } pub fn import_section<'a>(module: &'a mut elements::Module) -> Option<&'a mut elements::ImportSection> { for section in module.sections_mut() { - match section { - &mut elements::Section::Import(ref mut sect) => { - return Some(sect); - }, - _ => { } - } - } - None + match section { + &mut elements::Section::Import(ref mut sect) => { + return Some(sect); + }, + _ => { } + } + } + None } pub fn global_section<'a>(module: &'a mut elements::Module) -> Option<&'a mut elements::GlobalSection> { for section in module.sections_mut() { - match section { - &mut elements::Section::Global(ref mut sect) => { - return Some(sect); - }, - _ => { } - } - } - None + match section { + &mut elements::Section::Global(ref mut sect) => { + return Some(sect); + }, + _ => { } + } + } + None } pub fn functions_section<'a>(module: &'a mut elements::Module) -> Option<&'a mut elements::FunctionsSection> { for section in module.sections_mut() { - match section { - &mut elements::Section::Function(ref mut sect) => { - return Some(sect); - }, - _ => { } - } - } - None + match section { + &mut elements::Section::Function(ref mut sect) => { + return Some(sect); + }, + _ => { } + } + } + None } pub fn code_section<'a>(module: &'a mut elements::Module) -> Option<&'a mut elements::CodeSection> { for section in module.sections_mut() { - match section { - &mut elements::Section::Code(ref mut sect) => { - return Some(sect); - }, - _ => { } - } - } - None + match section { + &mut elements::Section::Code(ref mut sect) => { + return Some(sect); + }, + _ => { } + } + } + None } pub fn export_section<'a>(module: &'a mut elements::Module) -> Option<&'a mut elements::ExportSection> { for section in module.sections_mut() { - match section { - &mut elements::Section::Export(ref mut sect) => { - return Some(sect); - }, - _ => { } - } - } - None + match section { + &mut elements::Section::Export(ref mut sect) => { + return Some(sect); + }, + _ => { } + } + } + None } pub fn type_section<'a>(module: &'a mut elements::Module) -> Option<&'a mut elements::TypeSection> { for section in module.sections_mut() { - match section { - &mut elements::Section::Type(ref mut sect) => { - return Some(sect); - }, - _ => { } - } - } - None + match section { + &mut elements::Section::Type(ref mut sect) => { + return Some(sect); + }, + _ => { } + } + } + None } #[cfg(test)] mod tests { - use parity_wasm::{builder, elements}; - use super::*; + use parity_wasm::{builder, elements}; + use super::*; - /// @spec 0 - /// Optimizer presumes that export section exists and contains - /// all symbols passed as a second parameter. Since empty module - /// obviously contains no export section, optimizer should return - /// error on it. - #[test] - fn empty() { - let mut module = builder::module().build(); - let result = optimize(&mut module, vec!["_call"]); + /// @spec 0 + /// Optimizer presumes that export section exists and contains + /// all symbols passed as a second parameter. Since empty module + /// obviously contains no export section, optimizer should return + /// error on it. + #[test] + fn empty() { + let mut module = builder::module().build(); + let result = optimize(&mut module, vec!["_call"]); - assert!(result.is_err()); - } + assert!(result.is_err()); + } - /// @spec 1 - /// Imagine the unoptimized module has two own functions, `_call` and `_random` - /// and exports both of them in the export section. During optimization, the `_random` - /// function should vanish completely, given we pass `_call` as the only function to stay - /// in the module. - #[test] - fn minimal() { - let mut module = builder::module() - .function() - .signature().param().i32().build() - .build() - .function() - .signature() - .param().i32() - .param().i32() - .build() - .build() - .export() - .field("_call") - .internal().func(0).build() - .export() - .field("_random") - .internal().func(1).build() - .build(); - assert_eq!(module.export_section().expect("export section to be generated").entries().len(), 2); + /// @spec 1 + /// Imagine the unoptimized module has two own functions, `_call` and `_random` + /// and exports both of them in the export section. During optimization, the `_random` + /// function should vanish completely, given we pass `_call` as the only function to stay + /// in the module. + #[test] + fn minimal() { + let mut module = builder::module() + .function() + .signature().param().i32().build() + .build() + .function() + .signature() + .param().i32() + .param().i32() + .build() + .build() + .export() + .field("_call") + .internal().func(0).build() + .export() + .field("_random") + .internal().func(1).build() + .build(); + assert_eq!(module.export_section().expect("export section to be generated").entries().len(), 2); - optimize(&mut module, vec!["_call"]).expect("optimizer to succeed"); + optimize(&mut module, vec!["_call"]).expect("optimizer to succeed"); - assert_eq!( - 1, - module.export_section().expect("export section to be generated").entries().len(), - "There should only 1 (one) export entry in the optimized module" - ); + assert_eq!( + 1, + module.export_section().expect("export section to be generated").entries().len(), + "There should only 1 (one) export entry in the optimized module" + ); - assert_eq!( - 1, - module.functions_section().expect("functions section to be generated").entries().len(), - "There should 2 (two) functions in the optimized module" - ); - } + assert_eq!( + 1, + module.functions_section().expect("functions section to be generated").entries().len(), + "There should 2 (two) functions in the optimized module" + ); + } - /// @spec 2 - /// Imagine there is one exported function in unoptimized module, `_call`, that we specify as the one - /// to stay during the optimization. The code of this function uses global during the execution. - /// This sayed global should survive the optimization. - #[test] - fn globals() { - let mut module = builder::module() - .global() - .value_type().i32() - .build() - .function() - .signature().param().i32().build() - .body() - .with_opcodes(elements::Opcodes::new( - vec![ - elements::Opcode::GetGlobal(0), - elements::Opcode::End - ] - )) - .build() - .build() - .export() - .field("_call") - .internal().func(0).build() - .build(); + /// @spec 2 + /// Imagine there is one exported function in unoptimized module, `_call`, that we specify as the one + /// to stay during the optimization. The code of this function uses global during the execution. + /// This sayed global should survive the optimization. + #[test] + fn globals() { + let mut module = builder::module() + .global() + .value_type().i32() + .build() + .function() + .signature().param().i32().build() + .body() + .with_opcodes(elements::Opcodes::new( + vec![ + elements::Opcode::GetGlobal(0), + elements::Opcode::End + ] + )) + .build() + .build() + .export() + .field("_call") + .internal().func(0).build() + .build(); - optimize(&mut module, vec!["_call"]).expect("optimizer to succeed"); + optimize(&mut module, vec!["_call"]).expect("optimizer to succeed"); - assert_eq!( - 1, - module.global_section().expect("global section to be generated").entries().len(), - "There should 1 (one) global entry in the optimized module, since _call function uses it" - ); - } + assert_eq!( + 1, + module.global_section().expect("global section to be generated").entries().len(), + "There should 1 (one) global entry in the optimized module, since _call function uses it" + ); + } - /// @spec 2 - /// Imagine there is one exported function in unoptimized module, `_call`, that we specify as the one - /// to stay during the optimization. The code of this function uses one global during the execution, - /// but we have a bunch of other unused globals in the code. Last globals should not survive the optimization, - /// while the former should. - #[test] - fn globals_2() { - let mut module = builder::module() - .global() - .value_type().i32() - .build() - .global() - .value_type().i64() - .build() - .global() - .value_type().f32() - .build() - .function() - .signature().param().i32().build() - .body() - .with_opcodes(elements::Opcodes::new( - vec![ - elements::Opcode::GetGlobal(1), - elements::Opcode::End - ] - )) - .build() - .build() - .export() - .field("_call") - .internal().func(0).build() - .build(); + /// @spec 2 + /// Imagine there is one exported function in unoptimized module, `_call`, that we specify as the one + /// to stay during the optimization. The code of this function uses one global during the execution, + /// but we have a bunch of other unused globals in the code. Last globals should not survive the optimization, + /// while the former should. + #[test] + fn globals_2() { + let mut module = builder::module() + .global() + .value_type().i32() + .build() + .global() + .value_type().i64() + .build() + .global() + .value_type().f32() + .build() + .function() + .signature().param().i32().build() + .body() + .with_opcodes(elements::Opcodes::new( + vec![ + elements::Opcode::GetGlobal(1), + elements::Opcode::End + ] + )) + .build() + .build() + .export() + .field("_call") + .internal().func(0).build() + .build(); - optimize(&mut module, vec!["_call"]).expect("optimizer to succeed"); + optimize(&mut module, vec!["_call"]).expect("optimizer to succeed"); - assert_eq!( - 1, - module.global_section().expect("global section to be generated").entries().len(), - "There should 1 (one) global entry in the optimized module, since _call function uses only one" - ); - } + assert_eq!( + 1, + module.global_section().expect("global section to be generated").entries().len(), + "There should 1 (one) global entry in the optimized module, since _call function uses only one" + ); + } - /// @spec 3 - /// Imagine the unoptimized module has two own functions, `_call` and `_random` - /// and exports both of them in the export section. Function `_call` also calls `_random` - /// in its function body. The optimization should kick `_random` function from the export section - /// but preserve it's body. - #[test] - fn call_ref() { - let mut module = builder::module() - .function() - .signature().param().i32().build() - .body() - .with_opcodes(elements::Opcodes::new( - vec![ - elements::Opcode::Call(1), - elements::Opcode::End - ] - )) - .build() - .build() - .function() - .signature() - .param().i32() - .param().i32() - .build() - .build() - .export() - .field("_call") - .internal().func(0).build() - .export() - .field("_random") - .internal().func(1).build() - .build(); - assert_eq!(module.export_section().expect("export section to be generated").entries().len(), 2); + /// @spec 3 + /// Imagine the unoptimized module has two own functions, `_call` and `_random` + /// and exports both of them in the export section. Function `_call` also calls `_random` + /// in its function body. The optimization should kick `_random` function from the export section + /// but preserve it's body. + #[test] + fn call_ref() { + let mut module = builder::module() + .function() + .signature().param().i32().build() + .body() + .with_opcodes(elements::Opcodes::new( + vec![ + elements::Opcode::Call(1), + elements::Opcode::End + ] + )) + .build() + .build() + .function() + .signature() + .param().i32() + .param().i32() + .build() + .build() + .export() + .field("_call") + .internal().func(0).build() + .export() + .field("_random") + .internal().func(1).build() + .build(); + assert_eq!(module.export_section().expect("export section to be generated").entries().len(), 2); - optimize(&mut module, vec!["_call"]).expect("optimizer to succeed"); + optimize(&mut module, vec!["_call"]).expect("optimizer to succeed"); - assert_eq!( - 1, - module.export_section().expect("export section to be generated").entries().len(), - "There should only 1 (one) export entry in the optimized module" - ); + assert_eq!( + 1, + module.export_section().expect("export section to be generated").entries().len(), + "There should only 1 (one) export entry in the optimized module" + ); - assert_eq!( - 2, - module.functions_section().expect("functions section to be generated").entries().len(), - "There should 2 (two) functions in the optimized module" - ); - } + assert_eq!( + 2, + module.functions_section().expect("functions section to be generated").entries().len(), + "There should 2 (two) functions in the optimized module" + ); + } } \ No newline at end of file diff --git a/src/symbols.rs b/src/symbols.rs index b86efb9..29d47bc 100644 --- a/src/symbols.rs +++ b/src/symbols.rs @@ -3,150 +3,150 @@ use std::collections::HashSet; #[derive(PartialEq, Eq, Hash, Copy, Clone, Debug)] pub enum Symbol { - Type(usize), - Import(usize), - Global(usize), - Function(usize), - Export(usize), + Type(usize), + Import(usize), + Global(usize), + Function(usize), + Export(usize), } pub fn resolve_function(module: &elements::Module, index: u32) -> Symbol { - let mut functions = 0; - if let Some(import_section) = module.import_section() { - for (item_index, item) in import_section.entries().iter().enumerate() { - match item.external() { - &elements::External::Function(_) => { - if functions == index { - return Symbol::Import(item_index as usize); - } - functions += 1; - }, - _ => {} - } - } - } + let mut functions = 0; + if let Some(import_section) = module.import_section() { + for (item_index, item) in import_section.entries().iter().enumerate() { + match item.external() { + &elements::External::Function(_) => { + if functions == index { + return Symbol::Import(item_index as usize); + } + functions += 1; + }, + _ => {} + } + } + } - Symbol::Function(index as usize - functions as usize) + Symbol::Function(index as usize - functions as usize) } pub fn resolve_global(module: &elements::Module, index: u32) -> Symbol { - let mut globals = 0; - if let Some(import_section) = module.import_section() { - for (item_index, item) in import_section.entries().iter().enumerate() { - match item.external() { - &elements::External::Global(_) => { - if globals == index { - return Symbol::Import(item_index as usize); - } - globals += 1; - }, - _ => {} - } - } - } + let mut globals = 0; + if let Some(import_section) = module.import_section() { + for (item_index, item) in import_section.entries().iter().enumerate() { + match item.external() { + &elements::External::Global(_) => { + if globals == index { + return Symbol::Import(item_index as usize); + } + globals += 1; + }, + _ => {} + } + } + } - Symbol::Global(index as usize - globals as usize) + Symbol::Global(index as usize - globals as usize) } pub fn push_code_symbols(module: &elements::Module, opcodes: &[elements::Opcode], dest: &mut Vec) { - use parity_wasm::elements::Opcode::*; + use parity_wasm::elements::Opcode::*; - for opcode in opcodes { - match opcode { - &Call(idx) => { - dest.push(resolve_function(module, idx)); - }, - &GetGlobal(idx) | &SetGlobal(idx) => { - dest.push(resolve_global(module, idx)) - }, - &If(_, ref block) | &Loop(_, ref block) | &Block(_, ref block) => { - push_code_symbols(module, block.elements(), dest); - }, - _ => { }, - } - } + for opcode in opcodes { + match opcode { + &Call(idx) => { + dest.push(resolve_function(module, idx)); + }, + &GetGlobal(idx) | &SetGlobal(idx) => { + dest.push(resolve_global(module, idx)) + }, + &If(_, ref block) | &Loop(_, ref block) | &Block(_, ref block) => { + push_code_symbols(module, block.elements(), dest); + }, + _ => { }, + } + } } pub fn expand_symbols(module: &elements::Module, set: &mut HashSet) { - use self::Symbol::*; + use self::Symbol::*; - // symbols that were already processed - let mut stop: HashSet = HashSet::new(); - let mut fringe = set.iter().cloned().collect::>(); - loop { - let next = match fringe.pop() { - Some(s) if stop.contains(&s) => { continue; } - Some(s) => s, - _ => { break; } - }; - trace!("Processing symbol {:?}", next); + // symbols that were already processed + let mut stop: HashSet = HashSet::new(); + let mut fringe = set.iter().cloned().collect::>(); + loop { + let next = match fringe.pop() { + Some(s) if stop.contains(&s) => { continue; } + Some(s) => s, + _ => { break; } + }; + trace!("Processing symbol {:?}", next); - match next { - Export(idx) => { - let entry = &module.export_section().expect("Export section to exist").entries()[idx]; - match entry.internal() { - &elements::Internal::Function(func_idx) => { - let symbol = resolve_function(module, func_idx); - if !stop.contains(&symbol) { - fringe.push(symbol); - } - set.insert(symbol); - }, - &elements::Internal::Global(global_idx) => { - let symbol = resolve_global(module, global_idx); - if !stop.contains(&symbol) { - fringe.push(symbol); - } - set.insert(symbol); - }, - _ => {} - } - }, - Import(idx) => { - let entry = &module.import_section().expect("Import section to exist").entries()[idx]; - match entry.external() { - &elements::External::Function(type_idx) => { - let type_symbol = Symbol::Type(type_idx as usize); - if !stop.contains(&type_symbol) { - fringe.push(type_symbol); - } - set.insert(type_symbol); - }, - _ => {} - } - }, - Function(idx) => { - let body = &module.code_section().expect("Code section to exist").bodies()[idx]; - let mut code_symbols = Vec::new(); - push_code_symbols(module, body.code().elements(), &mut code_symbols); - for symbol in code_symbols.drain(..) { - if !stop.contains(&symbol) { - fringe.push(symbol); - } - set.insert(symbol); - } + match next { + Export(idx) => { + let entry = &module.export_section().expect("Export section to exist").entries()[idx]; + match entry.internal() { + &elements::Internal::Function(func_idx) => { + let symbol = resolve_function(module, func_idx); + if !stop.contains(&symbol) { + fringe.push(symbol); + } + set.insert(symbol); + }, + &elements::Internal::Global(global_idx) => { + let symbol = resolve_global(module, global_idx); + if !stop.contains(&symbol) { + fringe.push(symbol); + } + set.insert(symbol); + }, + _ => {} + } + }, + Import(idx) => { + let entry = &module.import_section().expect("Import section to exist").entries()[idx]; + match entry.external() { + &elements::External::Function(type_idx) => { + let type_symbol = Symbol::Type(type_idx as usize); + if !stop.contains(&type_symbol) { + fringe.push(type_symbol); + } + set.insert(type_symbol); + }, + _ => {} + } + }, + Function(idx) => { + let body = &module.code_section().expect("Code section to exist").bodies()[idx]; + let mut code_symbols = Vec::new(); + push_code_symbols(module, body.code().elements(), &mut code_symbols); + for symbol in code_symbols.drain(..) { + if !stop.contains(&symbol) { + fringe.push(symbol); + } + set.insert(symbol); + } - let signature = &module.functions_section().expect("Functions section to exist").entries()[idx]; - let type_symbol = Symbol::Type(signature.type_ref() as usize); - if !stop.contains(&type_symbol) { - fringe.push(type_symbol); - } - set.insert(type_symbol); - }, - Global(idx) => { - let entry = &module.global_section().expect("Global section to exist").entries()[idx]; - let mut code_symbols = Vec::new(); - push_code_symbols(module, entry.init_expr().code(), &mut code_symbols); - for symbol in code_symbols.drain(..) { - if !stop.contains(&symbol) { - fringe.push(symbol); - } - set.insert(symbol); - } - } - _ => {} - } + let signature = &module.functions_section().expect("Functions section to exist").entries()[idx]; + let type_symbol = Symbol::Type(signature.type_ref() as usize); + if !stop.contains(&type_symbol) { + fringe.push(type_symbol); + } + set.insert(type_symbol); + }, + Global(idx) => { + let entry = &module.global_section().expect("Global section to exist").entries()[idx]; + let mut code_symbols = Vec::new(); + push_code_symbols(module, entry.init_expr().code(), &mut code_symbols); + for symbol in code_symbols.drain(..) { + if !stop.contains(&symbol) { + fringe.push(symbol); + } + set.insert(symbol); + } + } + _ => {} + } - stop.insert(next); - } + stop.insert(next); + } } \ No newline at end of file