From 6bf31c03316a40e69cd38efbf93c8f70841f1d1c Mon Sep 17 00:00:00 2001 From: Dmitry Sinyavin Date: Wed, 27 Jul 2022 13:45:52 +0200 Subject: [PATCH] Backward compatibility and tracing --- Cargo.toml | 4 + src/lib.rs | 4 +- src/stack_limiter/max_height.rs | 138 ++++++++++++++++++++++---------- src/stack_limiter/mod.rs | 16 +++- 4 files changed, 119 insertions(+), 43 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 6508a1e..ff7a01d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,6 +22,9 @@ codegen-units = 1 [dependencies] parity-wasm = { version = "0.45", default-features = false } +log = { version = "0.4.8", default-features = false, optional = true } +test-log = { version = "0.2", optional = true } +env_logger = { version = "0.9", optional = true } [dev-dependencies] binaryen = "0.12" @@ -36,3 +39,4 @@ wasmprinter = "0.2" default = ["std"] std = ["parity-wasm/std"] sign_ext = ["parity-wasm/sign_ext"] +trace-log = ["dep:log", "dep:test-log", "dep:env_logger"] \ No newline at end of file diff --git a/src/lib.rs b/src/lib.rs index 9915704..eb4a129 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -8,4 +8,6 @@ mod stack_limiter; pub use export_globals::export_mutable_globals; pub use parity_wasm; -pub use stack_limiter::{compute_stack_cost, compute_stack_costs, inject as inject_stack_limiter}; \ No newline at end of file +pub use stack_limiter::{ + compute_stack_cost, compute_stack_height_weight, inject as inject_stack_limiter, +}; diff --git a/src/stack_limiter/max_height.rs b/src/stack_limiter/max_height.rs index a62c16e..812d5e3 100644 --- a/src/stack_limiter/max_height.rs +++ b/src/stack_limiter/max_height.rs @@ -1,10 +1,31 @@ use super::resolve_func_type; -use alloc::vec::Vec; +use alloc::{vec, vec::Vec}; use parity_wasm::elements::{self, BlockType, Type, ValueType}; #[cfg(feature = "sign_ext")] use parity_wasm::elements::SignExtInstruction; +#[cfg(feature = "trace-log")] +macro_rules! trace { + ($($tt:tt)*) => { + ::log::trace!($($tt)*); + }; +} + +#[cfg(not(feature = "trace-log"))] +macro_rules! trace { + ($($tt:tt)*) => {}; +} + +// The cost in stack items that should be charged per call of a function. This is +// 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_HEIGHT: u32 = 2; + +// Weight of an activation frame. +const ACTIVATION_FRAME_WEIGHT: u32 = 32; + /// Control stack frame. #[derive(Debug)] struct Frame { @@ -13,11 +34,11 @@ struct Frame { is_polymorphic: bool, /// Type of value which will be pushed after exiting - /// the current block or `None` if nothing is pushed. + /// the current block or `None` if block does not return a result. result_type: Option, /// Type of value which should be poped upon a branch to - /// this frame or `None` if nothing is popped. + /// this frame or `None` if branching shouldn't affect the stack. /// /// This might be diffirent from `result_type` since branch /// to the loop header can't take any values. @@ -69,24 +90,35 @@ impl Stack { /// Push control frame into the control stack. fn push_frame(&mut self, frame: Frame) { + trace!(" Push control frame {:?}", frame); self.control_stack.push(frame); } /// Pop control frame from the control stack. /// /// Returns `Err` if the control stack is empty. + + #[allow(clippy::let_and_return)] fn pop_frame(&mut self) -> Result { - self.control_stack.pop().ok_or("stack must be non-empty") + trace!(" Pop control frame"); + let frame = self.control_stack.pop().ok_or("stack must be non-empty"); + trace!(" {:?}", frame); + frame } /// Truncate the height of value stack to the specified height. fn trunc(&mut self, new_height: usize) { + trace!(" Truncate value stack to {}", new_height); self.values.truncate(new_height); } /// Push a value into the value stack. fn push_value(&mut self, value: ValueType) -> Result<(), &'static str> { + trace!(" Push {:?} to value stack", value); self.values.push(value); + if self.values.len() >= u32::MAX as usize { + return Err("stack overflow") + } Ok(()) } @@ -105,7 +137,9 @@ impl Stack { } if self.height() > 0 { - Ok(self.values.pop()) + let vt = self.values.pop(); + trace!("Pop {:?} from value stack", vt); + Ok(vt) } else { Err("trying to pop more values than pushed") } @@ -114,13 +148,13 @@ impl Stack { fn value_cost(val: ValueType) -> u32 { match val { - ValueType::I32 | ValueType::F32 => 1, - ValueType::I64 | ValueType::F64 => 2, + ValueType::I32 | ValueType::F32 => 4, + ValueType::I64 | ValueType::F64 => 8, } } /// This function expects the function to be validated. -pub fn compute(func_idx: u32, module: &elements::Module) -> Result { +pub fn compute(func_idx: u32, module: &elements::Module) -> Result<(u32, u32), &'static str> { use parity_wasm::elements::Instruction::*; let func_section = module.function_section().ok_or("No function section")?; @@ -151,19 +185,15 @@ pub fn compute(func_idx: u32, module: &elements::Module) -> Result = func_signature - .params() - .iter() - .cloned() - .chain(body.locals().iter().flat_map(|l| vec![l.value_type(); l.count() as usize])) - .collect(); + let mut locals = func_signature.params().to_vec(); + locals.extend(body.locals().iter().flat_map(|l| vec![l.value_type(); l.count() as usize])); let mut stack = Stack::new(); let mut max_weight: u32 = 0; - let mut pc = 0; + let mut max_height: usize = 0; // Add implicit frame for the function. Breaks to this frame and execution of // the last end should deal with this frame. @@ -179,12 +209,8 @@ pub fn compute(func_idx: u32, module: &elements::Module) -> Result= instructions.elements().len() { - break - } - - let opcode = &instructions.elements()[pc]; + for opcode in instructions.elements() { + trace!("Processing opcode {:?}", opcode); match opcode { Nop => {}, @@ -195,7 +221,9 @@ pub fn compute(func_idx: u32, module: &elements::Module) -> Result Result { stack.mark_unreachable()?; @@ -487,14 +518,36 @@ pub fn compute(func_idx: u32, module: &elements::Module) -> Result max_weight && !stack.frame(0)?.is_polymorphic { - max_weight = stack.weight(); + if !stack.frame(0)?.is_polymorphic { + let (cur_weight, cur_height) = (stack.weight(), stack.height()); + if cur_weight > max_weight { + max_weight = cur_weight; + trace!("Max weight is now {}", max_weight); + } + if cur_height > max_height { + max_height = cur_height; + trace!("Max height is now {}", max_height); + } } - pc += 1; + // Post-execution stage: pop a control frame if block is ended + if *opcode == End { + stack.pop_frame()?; + } } - Ok(max_weight + param_weight) + trace!("Final max stack height: {} + {}", ACTIVATION_FRAME_HEIGHT, max_height); + trace!( + "Final max stack weight: {} + {} + {}", + ACTIVATION_FRAME_WEIGHT, + max_weight, + param_weight + ); + + Ok(( + ACTIVATION_FRAME_HEIGHT + max_height as u32, + ACTIVATION_FRAME_WEIGHT + max_weight + param_weight, + )) } #[cfg(test)] @@ -502,6 +555,9 @@ mod tests { use super::*; use parity_wasm::elements; + #[cfg(feature = "trace-log")] + use test_log::test; + fn parse_wat(source: &str) -> elements::Module { elements::deserialize_buffer(&wat::parse_str(source).expect("Failed to wat2wasm")) .expect("Failed to deserialize the module") @@ -524,8 +580,8 @@ mod tests { "#, ); - let weight = compute(0, &module).unwrap(); - assert_eq!(weight, 3); + let res = compute(0, &module).unwrap(); + assert_eq!(res, (ACTIVATION_FRAME_HEIGHT + 3, ACTIVATION_FRAME_WEIGHT + 12)); } #[test] @@ -541,8 +597,8 @@ mod tests { "#, ); - let weight = compute(0, &module).unwrap(); - assert_eq!(weight, 2); + let res = compute(0, &module).unwrap(); + assert_eq!(res, (ACTIVATION_FRAME_HEIGHT + 1, ACTIVATION_FRAME_WEIGHT + 8)); } #[test] @@ -559,8 +615,8 @@ mod tests { "#, ); - let weight = compute(0, &module).unwrap(); - assert_eq!(weight, 0); + let res = compute(0, &module).unwrap(); + assert_eq!(res, (ACTIVATION_FRAME_HEIGHT, ACTIVATION_FRAME_WEIGHT)); } #[test] @@ -588,8 +644,8 @@ mod tests { "#, ); - let weight = compute(0, &module).unwrap(); - assert_eq!(weight, 2); + let res = compute(0, &module).unwrap(); + assert_eq!(res, (ACTIVATION_FRAME_HEIGHT + 2, ACTIVATION_FRAME_WEIGHT + 8)); } #[test] @@ -612,8 +668,8 @@ mod tests { "#, ); - let weight = compute(0, &module).unwrap(); - assert_eq!(weight, 1); + let res = compute(0, &module).unwrap(); + assert_eq!(res, (ACTIVATION_FRAME_HEIGHT + 1, ACTIVATION_FRAME_WEIGHT + 4)); } #[test] @@ -634,8 +690,8 @@ mod tests { "#, ); - let weight = compute(0, &module).unwrap(); - assert_eq!(weight, 1); + let res = compute(0, &module).unwrap(); + assert_eq!(res, (ACTIVATION_FRAME_HEIGHT + 1, ACTIVATION_FRAME_WEIGHT + 4)); } #[test] @@ -660,7 +716,7 @@ mod tests { "#, ); - let weight = compute(0, &module).unwrap(); - assert_eq!(weight, 3); + let res = compute(0, &module).unwrap(); + assert_eq!(res, (ACTIVATION_FRAME_HEIGHT + 3, ACTIVATION_FRAME_WEIGHT + 12)); } } diff --git a/src/stack_limiter/mod.rs b/src/stack_limiter/mod.rs index be4a377..904027f 100644 --- a/src/stack_limiter/mod.rs +++ b/src/stack_limiter/mod.rs @@ -194,13 +194,27 @@ pub fn compute_stack_cost(func_idx: u32, module: &elements::Module) -> Result Result<(u32, u32), &'static str> { + let func_imports = module.import_count(elements::ImportCountType::Function) as u32; + let defined_func_idx = func_idx + .checked_sub(func_imports) + .ok_or("This should be a index of a defined function")?; + + max_height::compute(defined_func_idx, module) +} + fn instrument_functions( ctx: &mut Context, module: &mut elements::Module,