diff --git a/Cargo.toml b/Cargo.toml index c33eecc..355dcba 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,6 +22,7 @@ codegen-units = 1 [dependencies] parity-wasm = { version = "0.45", default-features = false } +log = "0.4" [dev-dependencies] binaryen = "0.12" diff --git a/src/lib.rs b/src/lib.rs index aaf64e0..084d639 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,6 +1,8 @@ #![cfg_attr(not(feature = "std"), no_std)] extern crate alloc; +#[macro_use] +extern crate log; mod export_globals; pub mod gas_metering; @@ -8,4 +10,4 @@ mod stack_limiter; pub use export_globals::export_mutable_globals; pub use parity_wasm; -pub use stack_limiter::inject as inject_stack_limiter; +pub use stack_limiter::{compute_stack_cost, inject as inject_stack_limiter}; diff --git a/src/stack_limiter/max_height.rs b/src/stack_limiter/max_height.rs index 9e4d945..163392e 100644 --- a/src/stack_limiter/max_height.rs +++ b/src/stack_limiter/max_height.rs @@ -9,14 +9,13 @@ use parity_wasm::elements::SignExtInstruction; // is a static cost that is added to each function call. This makes sense because even // if a function does not use any parameters or locals some stack space on the host // machine might be consumed to hold some context. -const ACTIVATION_FRAME_COST: u32 = 2; +const ACTIVATION_FRAME_COST: u32 = 1; /// Control stack frame. #[derive(Debug)] struct Frame { - /// Stack becomes polymorphic only after an instruction that - /// never passes control further was executed. - is_polymorphic: bool, + /// Counts the nesting level of unreachable code. 0 if currently processed code is reachable + unreachable_depth: u32, /// Count of values which will be pushed after the exit /// from the current block. @@ -42,7 +41,7 @@ struct Stack { impl Stack { fn new() -> Stack { - Stack { height: ACTIVATION_FRAME_COST, control_stack: Vec::new() } + Stack { height: 0, control_stack: Vec::new() } } /// Returns current height of the value stack. @@ -50,6 +49,10 @@ impl Stack { self.height } + fn control_height(&self) -> u32 { + self.control_stack.len() as u32 + } + /// Returns a reference to a frame by specified depth relative to the top of /// control stack. fn frame(&self, rel_depth: u32) -> Result<&Frame, &'static str> { @@ -60,14 +63,27 @@ impl Stack { } /// Mark successive instructions as unreachable. - /// - /// This effectively makes stack polymorphic. fn mark_unreachable(&mut self) -> Result<(), &'static str> { - let top_frame = self.control_stack.last_mut().ok_or("stack must be non-empty")?; - top_frame.is_polymorphic = true; + let top_frame = self.control_stack.last_mut().ok_or("control stack must be non-empty")?; + top_frame.unreachable_depth = 1; Ok(()) } + /// Increase nesting level of unreachable code + fn push_unreachable(&mut self) -> Result<(), &'static str> { + let top_frame = self.control_stack.last_mut().ok_or("control stack must be non-empty")?; + top_frame.unreachable_depth += 1; + Ok(()) + } + + /// Decrease nesting level of unrechable code (probably making it reachable) + fn pop_unreachable(&mut self) -> Result { + let top_frame = self.control_stack.last_mut().ok_or("control stack must be non-empty")?; + top_frame.unreachable_depth = + top_frame.unreachable_depth.checked_sub(1).ok_or("unreachable code underflow")?; + Ok(top_frame.unreachable_depth) + } + /// Push control frame into the control stack. fn push_frame(&mut self, frame: Frame) { self.control_stack.push(frame); @@ -101,19 +117,6 @@ impl Stack { if value_count == 0 { return Ok(()) } - { - let top_frame = self.frame(0)?; - if self.height == top_frame.start_height { - // It is an error to pop more values than was pushed in the current frame - // (ie pop values pushed in the parent frame), unless the frame became - // polymorphic. - return if top_frame.is_polymorphic { - Ok(()) - } else { - return Err("trying to pop more values than pushed") - } - } - } self.height = self.height.checked_sub(value_count).ok_or("stack underflow")?; @@ -125,6 +128,8 @@ impl Stack { pub fn compute(func_idx: u32, module: &elements::Module) -> Result { use parity_wasm::elements::Instruction::*; + trace!("Processing function index {}", func_idx); + let func_section = module.function_section().ok_or("No function section")?; let code_section = module.code_section().ok_or("No code section")?; let type_section = module.type_section().ok_or("No type section")?; @@ -147,18 +152,62 @@ pub fn compute(func_idx: u32, module: &elements::Module) -> Result 0 { + match opcode { + Block(_) | Loop(_) | If(_) => { + trace!("Entering unreachable block {:?}", opcode); + stack.push_unreachable()?; + continue + }, + Else => { + let depth = stack.pop_unreachable()?; + if depth == 0 { + trace!("Transiting from unreachable If body to reachable Else block"); + } else { + trace!("Processing unreachable Else"); + stack.push_unreachable()?; + continue + } + }, + End => { + let depth = stack.pop_unreachable()?; + if depth == 0 { + trace!("Exiting unreachable code"); + } else { + trace!("Exiting unreachable block"); + continue + } + }, + _ => { + trace!("Skipping unreachable instruction {:?}", opcode); + continue + }, + } + } + + assert_eq!(stack.frame(0)?.unreachable_depth, 0); + trace!("Processing opcode {:?}", opcode); match opcode { Nop => {}, @@ -168,17 +217,17 @@ pub fn compute(func_idx: u32, module: &elements::Module) -> Result { - // The frame at the top should be pushed by `If`. So we leave - // it as is. + let frame = stack.pop_frame()?; + stack.trunc(frame.start_height); + stack.push_frame(frame); }, End => { let frame = stack.pop_frame()?; @@ -211,14 +260,6 @@ pub fn compute(func_idx: u32, module: &elements::Module) -> Result { let arity_of_default = stack.frame(br_table_data.default)?.branch_arity; - // Check that all jump targets have an equal arities. - for target in &*br_table_data.table { - let arity = stack.frame(*target)?.branch_arity; - if arity != arity_of_default { - return Err("Arity of all jump-targets must be equal") - } - } - // Because all jump targets have an equal arities, we can just take arity of // the default branch. stack.pop_values(arity_of_default)?; @@ -391,15 +432,36 @@ pub fn compute(func_idx: u32, module: &elements::Module) -> Result max_height && !stack.frame(0)?.is_polymorphic { + + if stack.height() > max_height { max_height = stack.height(); } + if stack.control_height() > max_control_height { + max_control_height = stack.control_height(); + } + trace!( + " Stack height: {}, control stack height: {}", + stack.height(), + stack.control_height() + ); } - Ok(max_height) + assert_eq!(stack.control_height(), 0); + assert_eq!(stack.height(), func_signature.results().len() as u32); + + trace!( + "Result: {} activation + {} stack + {} control stack + {} locals + {} params = {}", + ACTIVATION_FRAME_COST, + max_height, + max_control_height, + locals_count, + params_count, + ACTIVATION_FRAME_COST + max_height + max_control_height + locals_count + params_count + ); + + Ok(ACTIVATION_FRAME_COST + max_height + max_control_height + locals_count + params_count) } #[cfg(test)] @@ -430,7 +492,7 @@ mod tests { ); let height = compute(0, &module).unwrap(); - assert_eq!(height, 3 + ACTIVATION_FRAME_COST); + assert_eq!(height, ACTIVATION_FRAME_COST + 3 + 1 + 0 + 0); } #[test] @@ -447,7 +509,7 @@ mod tests { ); let height = compute(0, &module).unwrap(); - assert_eq!(height, 1 + ACTIVATION_FRAME_COST); + assert_eq!(height, ACTIVATION_FRAME_COST + 1 + 1 + 0 + 0); } #[test] @@ -465,7 +527,7 @@ mod tests { ); let height = compute(0, &module).unwrap(); - assert_eq!(height, ACTIVATION_FRAME_COST); + assert_eq!(height, ACTIVATION_FRAME_COST + 0 + 1 + 0 + 0); } #[test] diff --git a/src/stack_limiter/mod.rs b/src/stack_limiter/mod.rs index e207467..60034be 100644 --- a/src/stack_limiter/mod.rs +++ b/src/stack_limiter/mod.rs @@ -173,7 +173,7 @@ fn compute_stack_costs(module: &elements::Module) -> Result, &'static s /// Stack cost of the given *defined* function is the sum of it's locals count (that is, /// number of arguments plus number of local variables) and the maximal stack /// height. -fn compute_stack_cost(func_idx: u32, module: &elements::Module) -> Result { +pub fn compute_stack_cost(func_idx: u32, module: &elements::Module) -> Result { // To calculate the cost of a function we need to convert index from // function index space to defined function spaces. let func_imports = module.import_count(elements::ImportCountType::Function) as u32; @@ -181,36 +181,7 @@ fn compute_stack_cost(func_idx: u32, module: &elements::Module) -> Result