8 Commits

Author SHA1 Message Date
Dmitry Sinyavin 6bf31c0331 Backward compatibility and tracing 2022-07-27 13:45:52 +02:00
Dmitry Sinyavin 8a552c033c Fix failing pipeline checks 2022-07-26 11:38:58 +02:00
Dmitry Sinyavin c55ea7bfb7 Weighted stack metering 2022-07-26 10:52:14 +02:00
dependabot[bot] 3b932b11ad Update wasmparser requirement from 0.86 to 0.87 (#24)
Updates the requirements on [wasmparser](https://github.com/bytecodealliance/wasm-tools) to permit the latest version.
- [Release notes](https://github.com/bytecodealliance/wasm-tools/releases)
- [Commits](https://github.com/bytecodealliance/wasm-tools/compare/wasmparser-0.86.0...wasmparser-0.87.0)

---
updated-dependencies:
- dependency-name: wasmparser
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-07-06 11:21:44 +02:00
Alexander Theißen d10bbdf554 Fix CODEOWNERS 2022-06-21 14:21:59 +02:00
dependabot[bot] 28ef7f550c Update wasmparser requirement from 0.84 to 0.86 (#23)
Updates the requirements on [wasmparser](https://github.com/bytecodealliance/wasm-tools) to permit the latest version.
- [Release notes](https://github.com/bytecodealliance/wasm-tools/releases)
- [Commits](https://github.com/bytecodealliance/wasm-tools/compare/wasmparser-0.84.0...wasmparser-0.86.0)

---
updated-dependencies:
- dependency-name: wasmparser
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-06-12 20:24:01 +01:00
Filipe Azevedo 4a394c5f88 bump version (#22) 2022-06-08 10:29:51 +02:00
Alexander Theißen d1648be274 Fix publish 2022-06-06 16:16:30 +01:00
6 changed files with 325 additions and 160 deletions
+1 -1
View File
@@ -1,4 +1,4 @@
# For details about syntax, see: # For details about syntax, see:
# https://help.github.com/en/articles/about-code-owners # https://help.github.com/en/articles/about-code-owners
/ @athei @pepyakin * @athei @pepyakin
+1 -1
View File
@@ -16,7 +16,7 @@ The interface provided to smart contracts will adhere to semver with one excepti
major version bumps will be backwards compatible with regard to already deployed contracts. major version bumps will be backwards compatible with regard to already deployed contracts.
In other words: Upgrading this pallet will not break pre-existing contracts. In other words: Upgrading this pallet will not break pre-existing contracts.
## [v0.1.2] 2022-06-06 ## [v0.2.0] 2022-06-06
- Adjust debug information (if already parsed) when injecting gas metering - Adjust debug information (if already parsed) when injecting gas metering
[#16](https://github.com/paritytech/wasm-instrument/pull/16) [#16](https://github.com/paritytech/wasm-instrument/pull/16)
+7 -2
View File
@@ -1,6 +1,6 @@
[package] [package]
name = "wasm-instrument" name = "wasm-instrument"
version = "0.1.2" version = "0.2.0"
edition = "2021" edition = "2021"
rust-version = "1.56.1" rust-version = "1.56.1"
authors = ["Parity Technologies <admin@parity.io>"] authors = ["Parity Technologies <admin@parity.io>"]
@@ -14,6 +14,7 @@ include = ["src/**/*", "LICENSE-*", "README.md"]
[[bench]] [[bench]]
name = "benches" name = "benches"
harness = false harness = false
path = "benches/benches.rs"
[profile.bench] [profile.bench]
lto = "fat" lto = "fat"
@@ -21,6 +22,9 @@ codegen-units = 1
[dependencies] [dependencies]
parity-wasm = { version = "0.45", default-features = false } 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] [dev-dependencies]
binaryen = "0.12" binaryen = "0.12"
@@ -28,10 +32,11 @@ criterion = "0.3"
diff = "0.1" diff = "0.1"
rand = "0.8" rand = "0.8"
wat = "1" wat = "1"
wasmparser = "0.84" wasmparser = "0.87"
wasmprinter = "0.2" wasmprinter = "0.2"
[features] [features]
default = ["std"] default = ["std"]
std = ["parity-wasm/std"] std = ["parity-wasm/std"]
sign_ext = ["parity-wasm/sign_ext"] sign_ext = ["parity-wasm/sign_ext"]
trace-log = ["dep:log", "dep:test-log", "dep:env_logger"]
+3 -1
View File
@@ -8,4 +8,6 @@ mod stack_limiter;
pub use export_globals::export_mutable_globals; pub use export_globals::export_mutable_globals;
pub use parity_wasm; pub use parity_wasm;
pub use stack_limiter::inject as inject_stack_limiter; pub use stack_limiter::{
compute_stack_cost, compute_stack_height_weight, inject as inject_stack_limiter,
};
+296 -152
View File
@@ -1,15 +1,30 @@
use super::resolve_func_type; use super::resolve_func_type;
use alloc::vec::Vec; use alloc::{vec, vec::Vec};
use parity_wasm::elements::{self, BlockType, Type}; use parity_wasm::elements::{self, BlockType, Type, ValueType};
#[cfg(feature = "sign_ext")] #[cfg(feature = "sign_ext")]
use parity_wasm::elements::SignExtInstruction; 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 // 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 // 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 // if a function does not use any parameters or locals some stack space on the host
// machine might be consumed to hold some context. // machine might be consumed to hold some context.
const ACTIVATION_FRAME_COST: u32 = 2; const ACTIVATION_FRAME_HEIGHT: u32 = 2;
// Weight of an activation frame.
const ACTIVATION_FRAME_WEIGHT: u32 = 32;
/// Control stack frame. /// Control stack frame.
#[derive(Debug)] #[derive(Debug)]
@@ -18,36 +33,41 @@ struct Frame {
/// never passes control further was executed. /// never passes control further was executed.
is_polymorphic: bool, is_polymorphic: bool,
/// Count of values which will be pushed after the exit /// Type of value which will be pushed after exiting
/// from the current block. /// the current block or `None` if block does not return a result.
end_arity: u32, result_type: Option<ValueType>,
/// Count of values which should be poped upon a branch to /// Type of value which should be poped upon a branch to
/// this frame. /// this frame or `None` if branching shouldn't affect the stack.
/// ///
/// This might be diffirent from `end_arity` since branch /// This might be diffirent from `result_type` since branch
/// to the loop header can't take any values. /// to the loop header can't take any values.
branch_arity: u32, branch_type: Option<ValueType>,
/// Stack height before entering in the block. /// Stack height before entering in the block.
start_height: u32, start_height: usize,
} }
/// This is a compound stack that abstracts tracking height of the value stack /// This is a compound stack that abstracts tracking height and weight of the value stack
/// and manipulation of the control stack. /// and manipulation of the control stack.
struct Stack { struct Stack {
height: u32, values: Vec<ValueType>,
control_stack: Vec<Frame>, control_stack: Vec<Frame>,
} }
impl Stack { impl Stack {
fn new() -> Stack { fn new() -> Stack {
Stack { height: ACTIVATION_FRAME_COST, control_stack: Vec::new() } Stack { values: Vec::new(), control_stack: Vec::new() }
}
/// Returns current weight of the value stack.
fn weight(&self) -> u32 {
self.values.iter().map(|v| value_cost(*v)).sum()
} }
/// Returns current height of the value stack. /// Returns current height of the value stack.
fn height(&self) -> u32 { fn height(&self) -> usize {
self.height self.values.len()
} }
/// Returns a reference to a frame by specified depth relative to the top of /// Returns a reference to a frame by specified depth relative to the top of
@@ -70,59 +90,71 @@ impl Stack {
/// Push control frame into the control stack. /// Push control frame into the control stack.
fn push_frame(&mut self, frame: Frame) { fn push_frame(&mut self, frame: Frame) {
trace!(" Push control frame {:?}", frame);
self.control_stack.push(frame); self.control_stack.push(frame);
} }
/// Pop control frame from the control stack. /// Pop control frame from the control stack.
/// ///
/// Returns `Err` if the control stack is empty. /// Returns `Err` if the control stack is empty.
#[allow(clippy::let_and_return)]
fn pop_frame(&mut self) -> Result<Frame, &'static str> { fn pop_frame(&mut self) -> Result<Frame, &'static str> {
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. /// Truncate the height of value stack to the specified height.
fn trunc(&mut self, new_height: u32) { fn trunc(&mut self, new_height: usize) {
self.height = new_height; trace!(" Truncate value stack to {}", new_height);
self.values.truncate(new_height);
} }
/// Push specified number of values into the value stack. /// Push a value into the value stack.
/// fn push_value(&mut self, value: ValueType) -> Result<(), &'static str> {
/// Returns `Err` if the height overflow usize value. trace!(" Push {:?} to value stack", value);
fn push_values(&mut self, value_count: u32) -> Result<(), &'static str> { self.values.push(value);
self.height = self.height.checked_add(value_count).ok_or("stack overflow")?; if self.values.len() >= u32::MAX as usize {
return Err("stack overflow")
}
Ok(()) Ok(())
} }
/// Pop specified number of values from the value stack. /// Pop a value from the value stack.
/// ///
/// Returns `Err` if the stack happen to be negative value after /// Returns `Err` if the stack happen to be negative value after
/// values popped. /// value popped.
fn pop_values(&mut self, value_count: u32) -> Result<(), &'static str> { fn pop_value(&mut self) -> Result<Option<ValueType>, &'static str> {
if value_count == 0 { let top_frame = self.frame(0)?;
return Ok(()) if self.height() == top_frame.start_height {
} return if top_frame.is_polymorphic {
{ Ok(None)
let top_frame = self.frame(0)?; } else {
if self.height == top_frame.start_height { Err("trying to pop more values than pushed")
// 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")?; if self.height() > 0 {
let vt = self.values.pop();
trace!("Pop {:?} from value stack", vt);
Ok(vt)
} else {
Err("trying to pop more values than pushed")
}
}
}
Ok(()) fn value_cost(val: ValueType) -> u32 {
match val {
ValueType::I32 | ValueType::F32 => 4,
ValueType::I64 | ValueType::F64 => 8,
} }
} }
/// This function expects the function to be validated. /// This function expects the function to be validated.
pub fn compute(func_idx: u32, module: &elements::Module) -> Result<u32, &'static str> { pub fn compute(func_idx: u32, module: &elements::Module) -> Result<(u32, u32), &'static str> {
use parity_wasm::elements::Instruction::*; use parity_wasm::elements::Instruction::*;
let func_section = module.function_section().ok_or("No function section")?; let func_section = module.function_section().ok_or("No function section")?;
@@ -145,47 +177,55 @@ pub fn compute(func_idx: u32, module: &elements::Module) -> Result<u32, &'static
.ok_or("Function body for the index isn't found")?; .ok_or("Function body for the index isn't found")?;
let instructions = body.code(); let instructions = body.code();
// Get globals to resove their types
let globals: Vec<ValueType> = if let Some(global_section) = module.global_section() {
global_section
.entries()
.iter()
.map(|g| g.global_type().content_type())
.collect()
} else {
Vec::new()
};
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 stack = Stack::new();
let mut max_height: u32 = 0; 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 // Add implicit frame for the function. Breaks to this frame and execution of
// the last end should deal with this frame. // the last end should deal with this frame.
let func_arity = func_signature.results().len() as u32; let func_results = func_signature.results();
let param_weight: u32 = func_signature.params().iter().map(|v| value_cost(*v)).sum();
let func_result_type = if func_results.is_empty() { None } else { Some(func_results[0]) };
stack.push_frame(Frame { stack.push_frame(Frame {
is_polymorphic: false, is_polymorphic: false,
end_arity: func_arity, result_type: func_result_type,
branch_arity: func_arity, branch_type: func_result_type,
start_height: 0, start_height: 0,
}); });
loop { for opcode in instructions.elements() {
if pc >= instructions.elements().len() { trace!("Processing opcode {:?}", opcode);
break
}
// If current value stack is higher than maximal height observed so far,
// save the new height.
// However, we don't increase maximal value in unreachable code.
if stack.height() > max_height && !stack.frame(0)?.is_polymorphic {
max_height = stack.height();
}
let opcode = &instructions.elements()[pc];
match opcode { match opcode {
Nop => {}, Nop => {},
Block(ty) | Loop(ty) | If(ty) => { Block(ty) | Loop(ty) | If(ty) => {
let end_arity = if *ty == BlockType::NoResult { 0 } else { 1 };
let branch_arity = if let Loop(_) = *opcode { 0 } else { end_arity };
if let If(_) = *opcode { if let If(_) = *opcode {
stack.pop_values(1)?; stack.pop_value()?;
} }
let height = stack.height(); let height = stack.height();
let end_result = if let BlockType::Value(vt) = *ty { Some(vt) } else { None };
stack.push_frame(Frame { stack.push_frame(Frame {
is_polymorphic: false, is_polymorphic: stack.frame(0)?.is_polymorphic, /* Block inside unreachable
end_arity, * code is
branch_arity, * unreachable */
result_type: end_result,
branch_type: if let Loop(_) = *opcode { None } else { end_result },
start_height: height, start_height: height,
}); });
}, },
@@ -196,45 +236,56 @@ pub fn compute(func_idx: u32, module: &elements::Module) -> Result<u32, &'static
End => { End => {
let frame = stack.pop_frame()?; let frame = stack.pop_frame()?;
stack.trunc(frame.start_height); stack.trunc(frame.start_height);
stack.push_values(frame.end_arity)?; if let Some(vt) = frame.result_type {
stack.push_value(vt)?;
}
// Push the frame back for now to allow for stack calculations. We'll get rid of it
// later
stack.push_frame(frame);
}, },
Unreachable => { Unreachable => {
stack.mark_unreachable()?; stack.mark_unreachable()?;
}, },
Br(target) => { Br(target) => {
// Pop values for the destination block result. // Pop values for the destination block result.
let target_arity = stack.frame(*target)?.branch_arity; if stack.frame(*target)?.branch_type.is_some() {
stack.pop_values(target_arity)?; stack.pop_value()?;
}
// This instruction unconditionally transfers control to the specified block, // This instruction unconditionally transfers control to the specified block,
// thus all instruction until the end of the current block is deemed unreachable // thus all instruction until the end of the current block is deemed unreachable
stack.mark_unreachable()?; stack.mark_unreachable()?;
}, },
BrIf(target) => { BrIf(target) => {
let target_type = stack.frame(*target)?.branch_type;
// Pop values for the destination block result. // Pop values for the destination block result.
let target_arity = stack.frame(*target)?.branch_arity; if target_type.is_some() {
stack.pop_values(target_arity)?; stack.pop_value()?;
}
// Pop condition value. // Pop condition value.
stack.pop_values(1)?; stack.pop_value()?;
// Push values back. // Push values back.
stack.push_values(target_arity)?; if let Some(vt) = target_type {
stack.push_value(vt)?;
}
}, },
BrTable(br_table_data) => { BrTable(br_table_data) => {
let arity_of_default = stack.frame(br_table_data.default)?.branch_arity; let default_type = stack.frame(br_table_data.default)?.branch_type;
// Check that all jump targets have an equal arities. // Check that all jump targets have an equal arities.
for target in &*br_table_data.table { for target in &*br_table_data.table {
let arity = stack.frame(*target)?.branch_arity; if stack.frame(*target)?.branch_type != default_type {
if arity != arity_of_default { return Err("Types of all jump-targets must be equal")
return Err("Arity of all jump-targets must be equal")
} }
} }
// Because all jump targets have an equal arities, we can just take arity of // Because all jump targets have equal types, we can just take type of
// the default branch. // the default branch.
stack.pop_values(arity_of_default)?; if default_type.is_some() {
stack.pop_value()?;
}
// This instruction doesn't let control flow to go further, since the control flow // This instruction doesn't let control flow to go further, since the control flow
// should take either one of branches depending on the value or the default branch. // should take either one of branches depending on the value or the default branch.
@@ -243,80 +294,114 @@ pub fn compute(func_idx: u32, module: &elements::Module) -> Result<u32, &'static
Return => { Return => {
// Pop return values of the function. Mark successive instructions as unreachable // Pop return values of the function. Mark successive instructions as unreachable
// since this instruction doesn't let control flow to go further. // since this instruction doesn't let control flow to go further.
stack.pop_values(func_arity)?; if func_result_type.is_some() {
stack.pop_value()?;
}
stack.mark_unreachable()?; stack.mark_unreachable()?;
}, },
Call(idx) => { Call(idx) => {
let ty = resolve_func_type(*idx, module)?; let ty = resolve_func_type(*idx, module)?;
// Pop values for arguments of the function. // Pop values for arguments of the function.
stack.pop_values(ty.params().len() as u32)?; for _ in ty.params() {
stack.pop_value()?;
}
// Push result of the function execution to the stack. // Push result of the function execution to the stack.
let callee_arity = ty.results().len() as u32; let callee_results = ty.results();
stack.push_values(callee_arity)?; if !callee_results.is_empty() {
stack.push_value(callee_results[0])?;
}
}, },
CallIndirect(x, _) => { CallIndirect(x, _) => {
let Type::Function(ty) = let Type::Function(ty) =
type_section.types().get(*x as usize).ok_or("Type not found")?; type_section.types().get(*x as usize).ok_or("Type not found")?;
// Pop the offset into the function table. // Pop the offset into the function table.
stack.pop_values(1)?; stack.pop_value()?;
// Pop values for arguments of the function. // Pop values for arguments of the function.
stack.pop_values(ty.params().len() as u32)?; for _ in ty.params() {
stack.pop_value()?;
}
// Push result of the function execution to the stack. // Push result of the function execution to the stack.
let callee_arity = ty.results().len() as u32; let callee_results = ty.results();
stack.push_values(callee_arity)?; if !callee_results.is_empty() {
stack.push_value(callee_results[0])?;
}
}, },
Drop => { Drop => {
stack.pop_values(1)?; stack.pop_value()?;
}, },
Select => { Select => {
// Pop two values and one condition. // Pop two values and one condition.
stack.pop_values(2)?; let val = stack.pop_value()?;
stack.pop_values(1)?; stack.pop_value()?;
stack.pop_value()?;
// Push the selected value. // Push the selected value.
stack.push_values(1)?; if let Some(vt) = val {
stack.push_value(vt)?;
}
}, },
GetLocal(_) => { GetLocal(idx) => {
stack.push_values(1)?; let idx = *idx as usize;
if idx >= locals.len() {
return Err("Reference to a global is out of bounds")
}
stack.push_value(locals[idx])?;
}, },
SetLocal(_) => { SetLocal(_) => {
stack.pop_values(1)?; stack.pop_value()?;
}, },
TeeLocal(_) => { TeeLocal(idx) => {
// This instruction pops and pushes the value, so // This instruction pops and pushes the value, so
// effectively it doesn't modify the stack height. // effectively it doesn't modify the stack height.
stack.pop_values(1)?; let idx = *idx as usize;
stack.push_values(1)?; if idx >= locals.len() {
return Err("Reference to a local is out of bounds")
}
stack.pop_value()?;
stack.push_value(locals[idx])?;
}, },
GetGlobal(_) => { GetGlobal(idx) => {
stack.push_values(1)?; let idx = *idx as usize;
if idx >= globals.len() {
return Err("Reference to a global is out of bounds")
}
stack.push_value(globals[idx])?;
}, },
SetGlobal(_) => { SetGlobal(_) => {
stack.pop_values(1)?; stack.pop_value()?;
}, },
// These instructions pop the address and pushes the result
I32Load(_, _) | I32Load(_, _) |
I64Load(_, _) |
F32Load(_, _) |
F64Load(_, _) |
I32Load8S(_, _) | I32Load8S(_, _) |
I32Load8U(_, _) | I32Load8U(_, _) |
I32Load16S(_, _) | I32Load16S(_, _) |
I32Load16U(_, _) | I32Load16U(_, _) => {
stack.pop_value()?;
stack.push_value(ValueType::I32)?;
},
I64Load(_, _) |
I64Load8S(_, _) | I64Load8S(_, _) |
I64Load8U(_, _) | I64Load8U(_, _) |
I64Load16S(_, _) | I64Load16S(_, _) |
I64Load16U(_, _) | I64Load16U(_, _) |
I64Load32S(_, _) | I64Load32S(_, _) |
I64Load32U(_, _) => { I64Load32U(_, _) => {
// These instructions pop the address and pushes the result, stack.pop_value()?;
// which effictively don't modify the stack height. stack.push_value(ValueType::I64)?;
stack.pop_values(1)?; },
stack.push_values(1)?; F32Load(_, _) => {
stack.pop_value()?;
stack.push_value(ValueType::F32)?;
},
F64Load(_, _) => {
stack.pop_value()?;
stack.push_value(ValueType::F64)?;
}, },
I32Store(_, _) | I32Store(_, _) |
@@ -329,29 +414,38 @@ pub fn compute(func_idx: u32, module: &elements::Module) -> Result<u32, &'static
I64Store16(_, _) | I64Store16(_, _) |
I64Store32(_, _) => { I64Store32(_, _) => {
// These instructions pop the address and the value. // These instructions pop the address and the value.
stack.pop_values(2)?; stack.pop_value()?;
stack.pop_value()?;
}, },
CurrentMemory(_) => { CurrentMemory(_) => {
// Pushes current memory size // Pushes current memory size
stack.push_values(1)?; stack.push_value(ValueType::I32)?;
}, },
GrowMemory(_) => { GrowMemory(_) => {
// Grow memory takes the value of pages to grow and pushes // Grow memory takes the value of pages to grow and pushes
stack.pop_values(1)?; stack.pop_value()?;
stack.push_values(1)?; stack.push_value(ValueType::I32)?;
}, },
I32Const(_) | I64Const(_) | F32Const(_) | F64Const(_) => { I32Const(_) => {
// These instructions just push the single literal value onto the stack. stack.push_value(ValueType::I32)?;
stack.push_values(1)?; },
I64Const(_) => {
stack.push_value(ValueType::I64)?;
},
F32Const(_) => {
stack.push_value(ValueType::F32)?;
},
F64Const(_) => {
stack.push_value(ValueType::F64)?;
}, },
I32Eqz | I64Eqz => { I32Eqz | I64Eqz => {
// These instructions pop the value and compare it against zero, and pushes // These instructions pop the value and compare it against zero, and pushes
// the result of the comparison. // the result of the comparison.
stack.pop_values(1)?; stack.pop_value()?;
stack.push_values(1)?; stack.push_value(ValueType::I32)?;
}, },
I32Eq | I32Ne | I32LtS | I32LtU | I32GtS | I32GtU | I32LeS | I32LeU | I32GeS | I32Eq | I32Ne | I32LtS | I32LtU | I32GtS | I32GtU | I32LeS | I32LeU | I32GeS |
@@ -359,16 +453,18 @@ pub fn compute(func_idx: u32, module: &elements::Module) -> Result<u32, &'static
I64GeS | I64GeU | F32Eq | F32Ne | F32Lt | F32Gt | F32Le | F32Ge | F64Eq | F64Ne | I64GeS | I64GeU | F32Eq | F32Ne | F32Lt | F32Gt | F32Le | F32Ge | F64Eq | F64Ne |
F64Lt | F64Gt | F64Le | F64Ge => { F64Lt | F64Gt | F64Le | F64Ge => {
// Comparison operations take two operands and produce one result. // Comparison operations take two operands and produce one result.
stack.pop_values(2)?; stack.pop_value()?;
stack.push_values(1)?; stack.pop_value()?;
stack.push_value(ValueType::I32)?;
}, },
I32Clz | I32Ctz | I32Popcnt | I64Clz | I64Ctz | I64Popcnt | F32Abs | F32Neg | I32Clz | I32Ctz | I32Popcnt | I64Clz | I64Ctz | I64Popcnt | F32Abs | F32Neg |
F32Ceil | F32Floor | F32Trunc | F32Nearest | F32Sqrt | F64Abs | F64Neg | F64Ceil | F32Ceil | F32Floor | F32Trunc | F32Nearest | F32Sqrt | F64Abs | F64Neg | F64Ceil |
F64Floor | F64Trunc | F64Nearest | F64Sqrt => { F64Floor | F64Trunc | F64Nearest | F64Sqrt => {
// Unary operators take one operand and produce one result. // Unary operators take one operand and produce one result.
stack.pop_values(1)?; if let Some(vt) = stack.pop_value()? {
stack.push_values(1)?; stack.push_value(vt)?;
}
}, },
I32Add | I32Sub | I32Mul | I32DivS | I32DivU | I32RemS | I32RemU | I32And | I32Or | I32Add | I32Sub | I32Mul | I32DivS | I32DivU | I32RemS | I32RemU | I32And | I32Or |
@@ -378,19 +474,34 @@ pub fn compute(func_idx: u32, module: &elements::Module) -> Result<u32, &'static
F32Min | F32Max | F32Copysign | F64Add | F64Sub | F64Mul | F64Div | F64Min | F32Min | F32Max | F32Copysign | F64Add | F64Sub | F64Mul | F64Div | F64Min |
F64Max | F64Copysign => { F64Max | F64Copysign => {
// Binary operators take two operands and produce one result. // Binary operators take two operands and produce one result.
stack.pop_values(2)?; let val = stack.pop_value()?;
stack.push_values(1)?; stack.pop_value()?;
if let Some(vt) = val {
stack.push_value(vt)?;
}
}, },
// Conversion operators take one value and produce one result.
I32WrapI64 | I32TruncSF32 | I32TruncUF32 | I32TruncSF64 | I32TruncUF64 | I32WrapI64 | I32TruncSF32 | I32TruncUF32 | I32TruncSF64 | I32TruncUF64 |
I32ReinterpretF32 => {
stack.pop_value()?;
stack.push_value(ValueType::I32)?;
},
I64ExtendSI32 | I64ExtendUI32 | I64TruncSF32 | I64TruncUF32 | I64TruncSF64 | I64ExtendSI32 | I64ExtendUI32 | I64TruncSF32 | I64TruncUF32 | I64TruncSF64 |
I64TruncUF64 | F32ConvertSI32 | F32ConvertUI32 | F32ConvertSI64 | F32ConvertUI64 | I64TruncUF64 | I64ReinterpretF64 => {
F32DemoteF64 | F64ConvertSI32 | F64ConvertUI32 | F64ConvertSI64 | F64ConvertUI64 | stack.pop_value()?;
F64PromoteF32 | I32ReinterpretF32 | I64ReinterpretF64 | F32ReinterpretI32 | stack.push_value(ValueType::I64)?;
},
F32ConvertSI32 | F32ConvertUI32 | F32ConvertSI64 | F32ConvertUI64 | F32DemoteF64 |
F32ReinterpretI32 => {
stack.pop_value()?;
stack.push_value(ValueType::F32)?;
},
F64ConvertSI32 | F64ConvertUI32 | F64ConvertSI64 | F64ConvertUI64 | F64PromoteF32 |
F64ReinterpretI64 => { F64ReinterpretI64 => {
// Conversion operators take one value and produce one result. stack.pop_value()?;
stack.pop_values(1)?; stack.push_value(ValueType::F64)?;
stack.push_values(1)?;
}, },
#[cfg(feature = "sign_ext")] #[cfg(feature = "sign_ext")]
@@ -398,15 +509,45 @@ pub fn compute(func_idx: u32, module: &elements::Module) -> Result<u32, &'static
SignExt(SignExtInstruction::I32Extend16S) | SignExt(SignExtInstruction::I32Extend16S) |
SignExt(SignExtInstruction::I64Extend8S) | SignExt(SignExtInstruction::I64Extend8S) |
SignExt(SignExtInstruction::I64Extend16S) | SignExt(SignExtInstruction::I64Extend16S) |
SignExt(SignExtInstruction::I64Extend32S) => { SignExt(SignExtInstruction::I64Extend32S) =>
stack.pop_values(1)?; if let Some(vt) = stack.pop_value()? {
stack.push_values(1)?; stack.push_value(vt)?;
}, },
}
// If current value stack is heavier than maximal weight observed so far,
// save the new weight.
// However, we don't increase maximal value in unreachable code.
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);
}
}
// Post-execution stage: pop a control frame if block is ended
if *opcode == End {
stack.pop_frame()?;
} }
pc += 1;
} }
Ok(max_height) 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)] #[cfg(test)]
@@ -414,6 +555,9 @@ mod tests {
use super::*; use super::*;
use parity_wasm::elements; use parity_wasm::elements;
#[cfg(feature = "trace-log")]
use test_log::test;
fn parse_wat(source: &str) -> elements::Module { fn parse_wat(source: &str) -> elements::Module {
elements::deserialize_buffer(&wat::parse_str(source).expect("Failed to wat2wasm")) elements::deserialize_buffer(&wat::parse_str(source).expect("Failed to wat2wasm"))
.expect("Failed to deserialize the module") .expect("Failed to deserialize the module")
@@ -436,8 +580,8 @@ mod tests {
"#, "#,
); );
let height = compute(0, &module).unwrap(); let res = compute(0, &module).unwrap();
assert_eq!(height, 3 + ACTIVATION_FRAME_COST); assert_eq!(res, (ACTIVATION_FRAME_HEIGHT + 3, ACTIVATION_FRAME_WEIGHT + 12));
} }
#[test] #[test]
@@ -446,15 +590,15 @@ mod tests {
r#" r#"
(module (module
(func (result i32) (func (result i32)
i32.const 0 i64.const 0
return return
) )
) )
"#, "#,
); );
let height = compute(0, &module).unwrap(); let res = compute(0, &module).unwrap();
assert_eq!(height, 1 + ACTIVATION_FRAME_COST); assert_eq!(res, (ACTIVATION_FRAME_HEIGHT + 1, ACTIVATION_FRAME_WEIGHT + 8));
} }
#[test] #[test]
@@ -471,8 +615,8 @@ mod tests {
"#, "#,
); );
let height = compute(0, &module).unwrap(); let res = compute(0, &module).unwrap();
assert_eq!(height, ACTIVATION_FRAME_COST); assert_eq!(res, (ACTIVATION_FRAME_HEIGHT, ACTIVATION_FRAME_WEIGHT));
} }
#[test] #[test]
@@ -500,8 +644,8 @@ mod tests {
"#, "#,
); );
let height = compute(0, &module).unwrap(); let res = compute(0, &module).unwrap();
assert_eq!(height, 2 + ACTIVATION_FRAME_COST); assert_eq!(res, (ACTIVATION_FRAME_HEIGHT + 2, ACTIVATION_FRAME_WEIGHT + 8));
} }
#[test] #[test]
@@ -524,8 +668,8 @@ mod tests {
"#, "#,
); );
let height = compute(0, &module).unwrap(); let res = compute(0, &module).unwrap();
assert_eq!(height, 1 + ACTIVATION_FRAME_COST); assert_eq!(res, (ACTIVATION_FRAME_HEIGHT + 1, ACTIVATION_FRAME_WEIGHT + 4));
} }
#[test] #[test]
@@ -546,8 +690,8 @@ mod tests {
"#, "#,
); );
let height = compute(0, &module).unwrap(); let res = compute(0, &module).unwrap();
assert_eq!(height, 1 + ACTIVATION_FRAME_COST); assert_eq!(res, (ACTIVATION_FRAME_HEIGHT + 1, ACTIVATION_FRAME_WEIGHT + 4));
} }
#[test] #[test]
@@ -572,7 +716,7 @@ mod tests {
"#, "#,
); );
let height = compute(0, &module).unwrap(); let res = compute(0, &module).unwrap();
assert_eq!(height, 3 + ACTIVATION_FRAME_COST); assert_eq!(res, (ACTIVATION_FRAME_HEIGHT + 3, ACTIVATION_FRAME_WEIGHT + 12));
} }
} }
+17 -3
View File
@@ -154,7 +154,7 @@ fn generate_stack_height_global(module: &mut elements::Module) -> u32 {
/// Calculate stack costs for all functions. /// Calculate stack costs for all functions.
/// ///
/// Returns a vector with a stack cost for each function, including imports. /// Returns a vector with a stack cost for each function, including imports.
fn compute_stack_costs(module: &elements::Module) -> Result<Vec<u32>, &'static str> { pub fn compute_stack_costs(module: &elements::Module) -> Result<Vec<u32>, &'static str> {
let func_imports = module.import_count(elements::ImportCountType::Function); let func_imports = module.import_count(elements::ImportCountType::Function);
// TODO: optimize! // TODO: optimize!
@@ -173,7 +173,7 @@ fn compute_stack_costs(module: &elements::Module) -> Result<Vec<u32>, &'static s
/// Stack cost of the given *defined* function is the sum of it's locals count (that is, /// 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 /// number of arguments plus number of local variables) and the maximal stack
/// height. /// height.
fn compute_stack_cost(func_idx: u32, module: &elements::Module) -> Result<u32, &'static str> { pub fn compute_stack_cost(func_idx: u32, module: &elements::Module) -> Result<u32, &'static str> {
// To calculate the cost of a function we need to convert index from // To calculate the cost of a function we need to convert index from
// function index space to defined function spaces. // function index space to defined function spaces.
let func_imports = module.import_count(elements::ImportCountType::Function) as u32; let func_imports = module.import_count(elements::ImportCountType::Function) as u32;
@@ -194,13 +194,27 @@ fn compute_stack_cost(func_idx: u32, module: &elements::Module) -> Result<u32, &
locals_count.checked_add(local_group.count()).ok_or("Overflow in local count")?; locals_count.checked_add(local_group.count()).ok_or("Overflow in local count")?;
} }
let max_stack_height = max_height::compute(defined_func_idx, module)?; let (max_stack_height, _max_stack_weight) = max_height::compute(defined_func_idx, module)?;
locals_count locals_count
.checked_add(max_stack_height) .checked_add(max_stack_height)
.ok_or("Overflow in adding locals_count and max_stack_height") .ok_or("Overflow in adding locals_count and max_stack_height")
} }
/// Stack height is the measurement maximum wasm stack height reached during function execution.
/// Stack weight is weighted value which approximates a real stack size on x64 architecture.
pub fn compute_stack_height_weight(
func_idx: u32,
module: &elements::Module,
) -> 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( fn instrument_functions(
ctx: &mut Context, ctx: &mut Context,
module: &mut elements::Module, module: &mut elements::Module,