mirror of
https://github.com/pezkuwichain/wasm-instrument.git
synced 2026-04-23 00:17:55 +00:00
Compare commits
5 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 10fd3529f2 | |||
| 32ae9f8478 | |||
| 8bfdb41d01 | |||
| 659d3bf12c | |||
| 5b2f75a066 |
@@ -22,6 +22,7 @@ codegen-units = 1
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
parity-wasm = { version = "0.45", default-features = false }
|
parity-wasm = { version = "0.45", default-features = false }
|
||||||
|
log = "0.4"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
binaryen = "0.12"
|
binaryen = "0.12"
|
||||||
|
|||||||
+3
-1
@@ -1,6 +1,8 @@
|
|||||||
#![cfg_attr(not(feature = "std"), no_std)]
|
#![cfg_attr(not(feature = "std"), no_std)]
|
||||||
|
|
||||||
extern crate alloc;
|
extern crate alloc;
|
||||||
|
#[macro_use]
|
||||||
|
extern crate log;
|
||||||
|
|
||||||
mod export_globals;
|
mod export_globals;
|
||||||
pub mod gas_metering;
|
pub mod gas_metering;
|
||||||
@@ -8,4 +10,4 @@ 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, inject as inject_stack_limiter};
|
||||||
|
|||||||
+305
-204
@@ -9,14 +9,28 @@ use parity_wasm::elements::SignExtInstruction;
|
|||||||
// 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_COST: u32 = 1;
|
||||||
|
|
||||||
|
#[derive(Debug,PartialEq,Default,Clone,Copy)]
|
||||||
|
pub struct StackHeightStats {
|
||||||
|
pub activation_cost: u32,
|
||||||
|
pub max_height: u32,
|
||||||
|
pub max_control_height: u32,
|
||||||
|
pub locals_count: u32,
|
||||||
|
pub params_count: u32,
|
||||||
|
pub blocks_count: u32,
|
||||||
|
pub condbr_count: u32,
|
||||||
|
pub push_count: u32,
|
||||||
|
pub local_set_count: u32,
|
||||||
|
pub opcode_count: u32,
|
||||||
|
pub total_cost: u32,
|
||||||
|
}
|
||||||
|
|
||||||
/// Control stack frame.
|
/// Control stack frame.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
struct Frame {
|
struct Frame {
|
||||||
/// Stack becomes polymorphic only after an instruction that
|
/// Counts the nesting level of unreachable code. 0 if currently processed code is reachable
|
||||||
/// never passes control further was executed.
|
unreachable_depth: u32,
|
||||||
is_polymorphic: bool,
|
|
||||||
|
|
||||||
/// Count of values which will be pushed after the exit
|
/// Count of values which will be pushed after the exit
|
||||||
/// from the current block.
|
/// from the current block.
|
||||||
@@ -42,7 +56,7 @@ struct Stack {
|
|||||||
|
|
||||||
impl Stack {
|
impl Stack {
|
||||||
fn new() -> 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.
|
/// Returns current height of the value stack.
|
||||||
@@ -50,6 +64,10 @@ impl Stack {
|
|||||||
self.height
|
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
|
/// Returns a reference to a frame by specified depth relative to the top of
|
||||||
/// control stack.
|
/// control stack.
|
||||||
fn frame(&self, rel_depth: u32) -> Result<&Frame, &'static str> {
|
fn frame(&self, rel_depth: u32) -> Result<&Frame, &'static str> {
|
||||||
@@ -60,14 +78,27 @@ impl Stack {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Mark successive instructions as unreachable.
|
/// Mark successive instructions as unreachable.
|
||||||
///
|
|
||||||
/// This effectively makes stack polymorphic.
|
|
||||||
fn mark_unreachable(&mut self) -> Result<(), &'static str> {
|
fn mark_unreachable(&mut self) -> Result<(), &'static str> {
|
||||||
let top_frame = self.control_stack.last_mut().ok_or("stack must be non-empty")?;
|
let top_frame = self.control_stack.last_mut().ok_or("control stack must be non-empty")?;
|
||||||
top_frame.is_polymorphic = true;
|
top_frame.unreachable_depth = 1;
|
||||||
Ok(())
|
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<u32, &'static str> {
|
||||||
|
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.
|
/// Push control frame into the control stack.
|
||||||
fn push_frame(&mut self, frame: Frame) {
|
fn push_frame(&mut self, frame: Frame) {
|
||||||
self.control_stack.push(frame);
|
self.control_stack.push(frame);
|
||||||
@@ -101,19 +132,6 @@ impl Stack {
|
|||||||
if value_count == 0 {
|
if value_count == 0 {
|
||||||
return Ok(())
|
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")?;
|
self.height = self.height.checked_sub(value_count).ok_or("stack underflow")?;
|
||||||
|
|
||||||
@@ -122,9 +140,11 @@ impl Stack {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// 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<StackHeightStats, &'static str> {
|
||||||
use parity_wasm::elements::Instruction::*;
|
use parity_wasm::elements::Instruction::*;
|
||||||
|
|
||||||
|
trace!("Processing function index {}", func_idx);
|
||||||
|
|
||||||
let func_section = module.function_section().ok_or("No function section")?;
|
let func_section = module.function_section().ok_or("No function section")?;
|
||||||
let code_section = module.code_section().ok_or("No code section")?;
|
let code_section = module.code_section().ok_or("No code section")?;
|
||||||
let type_section = module.type_section().ok_or("No type section")?;
|
let type_section = module.type_section().ok_or("No type section")?;
|
||||||
@@ -147,31 +167,66 @@ pub fn compute(func_idx: u32, module: &elements::Module) -> Result<u32, &'static
|
|||||||
|
|
||||||
let mut stack = Stack::new();
|
let mut stack = Stack::new();
|
||||||
let mut max_height: u32 = 0;
|
let mut max_height: u32 = 0;
|
||||||
let mut pc = 0;
|
let mut max_control_height: u32 = 0;
|
||||||
|
|
||||||
|
let mut locals_count: u32 = 0;
|
||||||
|
for local_group in body.locals() {
|
||||||
|
locals_count =
|
||||||
|
locals_count.checked_add(local_group.count()).ok_or("Overflow in local count")?;
|
||||||
|
}
|
||||||
|
|
||||||
|
let params_count = func_signature.params().len() as u32;
|
||||||
|
let mut blocks_count = 0u32;
|
||||||
|
let mut condbr_count = 0u32;
|
||||||
|
let mut push_count = 0u32;
|
||||||
|
let mut local_set_count = 0u32;
|
||||||
|
|
||||||
// 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_arity = func_signature.results().len() as u32;
|
||||||
stack.push_frame(Frame {
|
stack.push_frame(Frame {
|
||||||
is_polymorphic: false,
|
unreachable_depth: 0,
|
||||||
end_arity: func_arity,
|
end_arity: func_arity,
|
||||||
branch_arity: func_arity,
|
branch_arity: func_arity,
|
||||||
start_height: 0,
|
start_height: 0,
|
||||||
});
|
});
|
||||||
|
|
||||||
loop {
|
for opcode in instructions.elements() {
|
||||||
if pc >= instructions.elements().len() {
|
if stack.frame(0)?.unreachable_depth > 0 {
|
||||||
break
|
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
|
||||||
|
},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// If current value stack is higher than maximal height observed so far,
|
assert_eq!(stack.frame(0)?.unreachable_depth, 0);
|
||||||
// save the new height.
|
trace!("Processing opcode {:?}", opcode);
|
||||||
// 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 => {},
|
||||||
@@ -181,17 +236,18 @@ pub fn compute(func_idx: u32, module: &elements::Module) -> Result<u32, &'static
|
|||||||
if let If(_) = *opcode {
|
if let If(_) = *opcode {
|
||||||
stack.pop_values(1)?;
|
stack.pop_values(1)?;
|
||||||
}
|
}
|
||||||
let height = stack.height();
|
|
||||||
stack.push_frame(Frame {
|
stack.push_frame(Frame {
|
||||||
is_polymorphic: false,
|
unreachable_depth: 0,
|
||||||
end_arity,
|
end_arity,
|
||||||
branch_arity,
|
branch_arity,
|
||||||
start_height: height,
|
start_height: stack.height(),
|
||||||
});
|
});
|
||||||
|
blocks_count += 1;
|
||||||
},
|
},
|
||||||
Else => {
|
Else => {
|
||||||
// The frame at the top should be pushed by `If`. So we leave
|
let frame = stack.pop_frame()?;
|
||||||
// it as is.
|
stack.trunc(frame.start_height);
|
||||||
|
stack.push_frame(frame);
|
||||||
},
|
},
|
||||||
End => {
|
End => {
|
||||||
let frame = stack.pop_frame()?;
|
let frame = stack.pop_frame()?;
|
||||||
@@ -220,18 +276,12 @@ pub fn compute(func_idx: u32, module: &elements::Module) -> Result<u32, &'static
|
|||||||
|
|
||||||
// Push values back.
|
// Push values back.
|
||||||
stack.push_values(target_arity)?;
|
stack.push_values(target_arity)?;
|
||||||
|
|
||||||
|
condbr_count += 1;
|
||||||
},
|
},
|
||||||
BrTable(br_table_data) => {
|
BrTable(br_table_data) => {
|
||||||
let arity_of_default = stack.frame(br_table_data.default)?.branch_arity;
|
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
|
// Because all jump targets have an equal arities, we can just take arity of
|
||||||
// the default branch.
|
// the default branch.
|
||||||
stack.pop_values(arity_of_default)?;
|
stack.pop_values(arity_of_default)?;
|
||||||
@@ -239,6 +289,8 @@ pub fn compute(func_idx: u32, module: &elements::Module) -> Result<u32, &'static
|
|||||||
// 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.
|
||||||
stack.mark_unreachable()?;
|
stack.mark_unreachable()?;
|
||||||
|
|
||||||
|
condbr_count += 1;
|
||||||
},
|
},
|
||||||
Return => {
|
Return => {
|
||||||
// Pop return values of the function. Mark successive instructions as unreachable
|
// Pop return values of the function. Mark successive instructions as unreachable
|
||||||
@@ -280,21 +332,26 @@ pub fn compute(func_idx: u32, module: &elements::Module) -> Result<u32, &'static
|
|||||||
|
|
||||||
// Push the selected value.
|
// Push the selected value.
|
||||||
stack.push_values(1)?;
|
stack.push_values(1)?;
|
||||||
|
push_count += 1;
|
||||||
},
|
},
|
||||||
GetLocal(_) => {
|
GetLocal(_) => {
|
||||||
stack.push_values(1)?;
|
stack.push_values(1)?;
|
||||||
|
push_count += 1;
|
||||||
},
|
},
|
||||||
SetLocal(_) => {
|
SetLocal(_) => {
|
||||||
stack.pop_values(1)?;
|
stack.pop_values(1)?;
|
||||||
|
local_set_count += 1;
|
||||||
},
|
},
|
||||||
TeeLocal(_) => {
|
TeeLocal(_) => {
|
||||||
// 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)?;
|
stack.pop_values(1)?;
|
||||||
stack.push_values(1)?;
|
stack.push_values(1)?;
|
||||||
|
local_set_count += 1;
|
||||||
},
|
},
|
||||||
GetGlobal(_) => {
|
GetGlobal(_) => {
|
||||||
stack.push_values(1)?;
|
stack.push_values(1)?;
|
||||||
|
push_count += 1;
|
||||||
},
|
},
|
||||||
SetGlobal(_) => {
|
SetGlobal(_) => {
|
||||||
stack.pop_values(1)?;
|
stack.pop_values(1)?;
|
||||||
@@ -317,6 +374,7 @@ pub fn compute(func_idx: u32, module: &elements::Module) -> Result<u32, &'static
|
|||||||
// which effictively don't modify the stack height.
|
// which effictively don't modify the stack height.
|
||||||
stack.pop_values(1)?;
|
stack.pop_values(1)?;
|
||||||
stack.push_values(1)?;
|
stack.push_values(1)?;
|
||||||
|
push_count += 1;
|
||||||
},
|
},
|
||||||
|
|
||||||
I32Store(_, _) |
|
I32Store(_, _) |
|
||||||
@@ -335,16 +393,19 @@ pub fn compute(func_idx: u32, module: &elements::Module) -> Result<u32, &'static
|
|||||||
CurrentMemory(_) => {
|
CurrentMemory(_) => {
|
||||||
// Pushes current memory size
|
// Pushes current memory size
|
||||||
stack.push_values(1)?;
|
stack.push_values(1)?;
|
||||||
|
push_count += 1;
|
||||||
},
|
},
|
||||||
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_values(1)?;
|
||||||
stack.push_values(1)?;
|
stack.push_values(1)?;
|
||||||
|
push_count += 1;
|
||||||
},
|
},
|
||||||
|
|
||||||
I32Const(_) | I64Const(_) | F32Const(_) | F64Const(_) => {
|
I32Const(_) | I64Const(_) | F32Const(_) | F64Const(_) => {
|
||||||
// These instructions just push the single literal value onto the stack.
|
// These instructions just push the single literal value onto the stack.
|
||||||
stack.push_values(1)?;
|
stack.push_values(1)?;
|
||||||
|
push_count += 1;
|
||||||
},
|
},
|
||||||
|
|
||||||
I32Eqz | I64Eqz => {
|
I32Eqz | I64Eqz => {
|
||||||
@@ -352,6 +413,7 @@ pub fn compute(func_idx: u32, module: &elements::Module) -> Result<u32, &'static
|
|||||||
// the result of the comparison.
|
// the result of the comparison.
|
||||||
stack.pop_values(1)?;
|
stack.pop_values(1)?;
|
||||||
stack.push_values(1)?;
|
stack.push_values(1)?;
|
||||||
|
push_count += 1;
|
||||||
},
|
},
|
||||||
|
|
||||||
I32Eq | I32Ne | I32LtS | I32LtU | I32GtS | I32GtU | I32LeS | I32LeU | I32GeS |
|
I32Eq | I32Ne | I32LtS | I32LtU | I32GtS | I32GtU | I32LeS | I32LeU | I32GeS |
|
||||||
@@ -361,6 +423,7 @@ pub fn compute(func_idx: u32, module: &elements::Module) -> Result<u32, &'static
|
|||||||
// Comparison operations take two operands and produce one result.
|
// Comparison operations take two operands and produce one result.
|
||||||
stack.pop_values(2)?;
|
stack.pop_values(2)?;
|
||||||
stack.push_values(1)?;
|
stack.push_values(1)?;
|
||||||
|
push_count += 1;
|
||||||
},
|
},
|
||||||
|
|
||||||
I32Clz | I32Ctz | I32Popcnt | I64Clz | I64Ctz | I64Popcnt | F32Abs | F32Neg |
|
I32Clz | I32Ctz | I32Popcnt | I64Clz | I64Ctz | I64Popcnt | F32Abs | F32Neg |
|
||||||
@@ -369,6 +432,7 @@ pub fn compute(func_idx: u32, module: &elements::Module) -> Result<u32, &'static
|
|||||||
// Unary operators take one operand and produce one result.
|
// Unary operators take one operand and produce one result.
|
||||||
stack.pop_values(1)?;
|
stack.pop_values(1)?;
|
||||||
stack.push_values(1)?;
|
stack.push_values(1)?;
|
||||||
|
push_count += 1;
|
||||||
},
|
},
|
||||||
|
|
||||||
I32Add | I32Sub | I32Mul | I32DivS | I32DivU | I32RemS | I32RemU | I32And | I32Or |
|
I32Add | I32Sub | I32Mul | I32DivS | I32DivU | I32RemS | I32RemU | I32And | I32Or |
|
||||||
@@ -380,6 +444,7 @@ pub fn compute(func_idx: u32, module: &elements::Module) -> Result<u32, &'static
|
|||||||
// Binary operators take two operands and produce one result.
|
// Binary operators take two operands and produce one result.
|
||||||
stack.pop_values(2)?;
|
stack.pop_values(2)?;
|
||||||
stack.push_values(1)?;
|
stack.push_values(1)?;
|
||||||
|
push_count += 1;
|
||||||
},
|
},
|
||||||
|
|
||||||
I32WrapI64 | I32TruncSF32 | I32TruncUF32 | I32TruncSF64 | I32TruncUF64 |
|
I32WrapI64 | I32TruncSF32 | I32TruncUF32 | I32TruncSF64 | I32TruncUF64 |
|
||||||
@@ -391,6 +456,7 @@ pub fn compute(func_idx: u32, module: &elements::Module) -> Result<u32, &'static
|
|||||||
// Conversion operators take one value and produce one result.
|
// Conversion operators take one value and produce one result.
|
||||||
stack.pop_values(1)?;
|
stack.pop_values(1)?;
|
||||||
stack.push_values(1)?;
|
stack.push_values(1)?;
|
||||||
|
push_count += 1;
|
||||||
},
|
},
|
||||||
|
|
||||||
#[cfg(feature = "sign_ext")]
|
#[cfg(feature = "sign_ext")]
|
||||||
@@ -401,178 +467,213 @@ pub fn compute(func_idx: u32, module: &elements::Module) -> Result<u32, &'static
|
|||||||
SignExt(SignExtInstruction::I64Extend32S) => {
|
SignExt(SignExtInstruction::I64Extend32S) => {
|
||||||
stack.pop_values(1)?;
|
stack.pop_values(1)?;
|
||||||
stack.push_values(1)?;
|
stack.push_values(1)?;
|
||||||
|
push_count += 1;
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
pc += 1;
|
|
||||||
|
// If current value/control stack is higher than maximal height observed so far,
|
||||||
|
// save the new height.
|
||||||
|
|
||||||
|
if stack.height() > max_height {
|
||||||
|
max_height = stack.height();
|
||||||
}
|
}
|
||||||
|
if stack.control_height() > max_control_height {
|
||||||
Ok(max_height)
|
max_control_height = stack.control_height();
|
||||||
}
|
}
|
||||||
|
trace!(
|
||||||
#[cfg(test)]
|
" Stack height: {}, control stack height: {}",
|
||||||
mod tests {
|
stack.height(),
|
||||||
use super::*;
|
stack.control_height()
|
||||||
use parity_wasm::elements;
|
|
||||||
|
|
||||||
fn parse_wat(source: &str) -> elements::Module {
|
|
||||||
elements::deserialize_buffer(&wat::parse_str(source).expect("Failed to wat2wasm"))
|
|
||||||
.expect("Failed to deserialize the module")
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn simple_test() {
|
|
||||||
let module = parse_wat(
|
|
||||||
r#"
|
|
||||||
(module
|
|
||||||
(func
|
|
||||||
i32.const 1
|
|
||||||
i32.const 2
|
|
||||||
i32.const 3
|
|
||||||
drop
|
|
||||||
drop
|
|
||||||
drop
|
|
||||||
)
|
|
||||||
)
|
|
||||||
"#,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
let height = compute(0, &module).unwrap();
|
|
||||||
assert_eq!(height, 3 + ACTIVATION_FRAME_COST);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
assert_eq!(stack.control_height(), 0);
|
||||||
fn implicit_and_explicit_return() {
|
assert_eq!(stack.height(), func_signature.results().len() as u32);
|
||||||
let module = parse_wat(
|
|
||||||
r#"
|
|
||||||
(module
|
|
||||||
(func (result i32)
|
|
||||||
i32.const 0
|
|
||||||
return
|
|
||||||
)
|
|
||||||
)
|
|
||||||
"#,
|
|
||||||
);
|
|
||||||
|
|
||||||
let height = compute(0, &module).unwrap();
|
let res = StackHeightStats {
|
||||||
assert_eq!(height, 1 + ACTIVATION_FRAME_COST);
|
activation_cost: ACTIVATION_FRAME_COST,
|
||||||
|
max_height: max_height,
|
||||||
|
max_control_height: max_control_height,
|
||||||
|
locals_count: locals_count,
|
||||||
|
params_count: params_count,
|
||||||
|
blocks_count: blocks_count,
|
||||||
|
condbr_count: condbr_count,
|
||||||
|
push_count: push_count,
|
||||||
|
local_set_count: local_set_count,
|
||||||
|
opcode_count: instructions.elements().len() as u32,
|
||||||
|
// total_cost: (11.749 * params_count as f64 - 0.4888 * locals_count as f64 + 14.8169 * max_height as f64 - 5.1594 * max_control_height as f64 - 24.4941) as u32
|
||||||
|
total_cost: ACTIVATION_FRAME_COST + 2 * max_height + max_control_height + locals_count + 2 * params_count,
|
||||||
|
};
|
||||||
|
|
||||||
|
trace!("Result: {:?}", res);
|
||||||
|
|
||||||
|
Ok(res)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
// #[cfg(test)]
|
||||||
fn dont_count_in_unreachable() {
|
// mod tests {
|
||||||
let module = parse_wat(
|
// use super::*;
|
||||||
r#"
|
// use parity_wasm::elements;
|
||||||
(module
|
|
||||||
(memory 0)
|
|
||||||
(func (result i32)
|
|
||||||
unreachable
|
|
||||||
grow_memory
|
|
||||||
)
|
|
||||||
)
|
|
||||||
"#,
|
|
||||||
);
|
|
||||||
|
|
||||||
let height = compute(0, &module).unwrap();
|
// fn parse_wat(source: &str) -> elements::Module {
|
||||||
assert_eq!(height, ACTIVATION_FRAME_COST);
|
// elements::deserialize_buffer(&wat::parse_str(source).expect("Failed to wat2wasm"))
|
||||||
}
|
// .expect("Failed to deserialize the module")
|
||||||
|
// }
|
||||||
|
|
||||||
#[test]
|
// #[test]
|
||||||
fn yet_another_test() {
|
// fn simple_test() {
|
||||||
let module = parse_wat(
|
// let module = parse_wat(
|
||||||
r#"
|
// r#"
|
||||||
(module
|
// (module
|
||||||
(memory 0)
|
// (func
|
||||||
(func
|
// i32.const 1
|
||||||
;; Push two values and then pop them.
|
// i32.const 2
|
||||||
;; This will make max depth to be equal to 2.
|
// i32.const 3
|
||||||
i32.const 0
|
// drop
|
||||||
i32.const 1
|
// drop
|
||||||
drop
|
// drop
|
||||||
drop
|
// )
|
||||||
|
// )
|
||||||
|
// "#,
|
||||||
|
// );
|
||||||
|
|
||||||
;; Code after `unreachable` shouldn't have an effect
|
// let height = compute(0, &module).unwrap();
|
||||||
;; on the max depth.
|
// assert_eq!(height, ACTIVATION_FRAME_COST + 3 + 1 + 0 + 0);
|
||||||
unreachable
|
// }
|
||||||
i32.const 0
|
|
||||||
i32.const 1
|
|
||||||
i32.const 2
|
|
||||||
)
|
|
||||||
)
|
|
||||||
"#,
|
|
||||||
);
|
|
||||||
|
|
||||||
let height = compute(0, &module).unwrap();
|
// #[test]
|
||||||
assert_eq!(height, 2 + ACTIVATION_FRAME_COST);
|
// fn implicit_and_explicit_return() {
|
||||||
}
|
// let module = parse_wat(
|
||||||
|
// r#"
|
||||||
|
// (module
|
||||||
|
// (func (result i32)
|
||||||
|
// i32.const 0
|
||||||
|
// return
|
||||||
|
// )
|
||||||
|
// )
|
||||||
|
// "#,
|
||||||
|
// );
|
||||||
|
|
||||||
#[test]
|
// let height = compute(0, &module).unwrap();
|
||||||
fn call_indirect() {
|
// assert_eq!(height, ACTIVATION_FRAME_COST + 1 + 1 + 0 + 0);
|
||||||
let module = parse_wat(
|
// }
|
||||||
r#"
|
|
||||||
(module
|
|
||||||
(table $ptr 1 1 funcref)
|
|
||||||
(elem $ptr (i32.const 0) func 1)
|
|
||||||
(func $main
|
|
||||||
(call_indirect (i32.const 0))
|
|
||||||
(call_indirect (i32.const 0))
|
|
||||||
(call_indirect (i32.const 0))
|
|
||||||
)
|
|
||||||
(func $callee
|
|
||||||
i64.const 42
|
|
||||||
drop
|
|
||||||
)
|
|
||||||
)
|
|
||||||
"#,
|
|
||||||
);
|
|
||||||
|
|
||||||
let height = compute(0, &module).unwrap();
|
// #[test]
|
||||||
assert_eq!(height, 1 + ACTIVATION_FRAME_COST);
|
// fn dont_count_in_unreachable() {
|
||||||
}
|
// let module = parse_wat(
|
||||||
|
// r#"
|
||||||
|
// (module
|
||||||
|
// (memory 0)
|
||||||
|
// (func (result i32)
|
||||||
|
// unreachable
|
||||||
|
// grow_memory
|
||||||
|
// )
|
||||||
|
// )
|
||||||
|
// "#,
|
||||||
|
// );
|
||||||
|
|
||||||
#[test]
|
// let height = compute(0, &module).unwrap();
|
||||||
fn breaks() {
|
// assert_eq!(height, ACTIVATION_FRAME_COST + 0 + 1 + 0 + 0);
|
||||||
let module = parse_wat(
|
// }
|
||||||
r#"
|
|
||||||
(module
|
|
||||||
(func $main
|
|
||||||
block (result i32)
|
|
||||||
block (result i32)
|
|
||||||
i32.const 99
|
|
||||||
br 1
|
|
||||||
end
|
|
||||||
end
|
|
||||||
drop
|
|
||||||
)
|
|
||||||
)
|
|
||||||
"#,
|
|
||||||
);
|
|
||||||
|
|
||||||
let height = compute(0, &module).unwrap();
|
// #[test]
|
||||||
assert_eq!(height, 1 + ACTIVATION_FRAME_COST);
|
// fn yet_another_test() {
|
||||||
}
|
// let module = parse_wat(
|
||||||
|
// r#"
|
||||||
|
// (module
|
||||||
|
// (memory 0)
|
||||||
|
// (func
|
||||||
|
// ;; Push two values and then pop them.
|
||||||
|
// ;; This will make max depth to be equal to 2.
|
||||||
|
// i32.const 0
|
||||||
|
// i32.const 1
|
||||||
|
// drop
|
||||||
|
// drop
|
||||||
|
|
||||||
#[test]
|
// ;; Code after `unreachable` shouldn't have an effect
|
||||||
fn if_else_works() {
|
// ;; on the max depth.
|
||||||
let module = parse_wat(
|
// unreachable
|
||||||
r#"
|
// i32.const 0
|
||||||
(module
|
// i32.const 1
|
||||||
(func $main
|
// i32.const 2
|
||||||
i32.const 7
|
// )
|
||||||
i32.const 1
|
// )
|
||||||
if (result i32)
|
// "#,
|
||||||
i32.const 42
|
// );
|
||||||
else
|
|
||||||
i32.const 99
|
|
||||||
end
|
|
||||||
i32.const 97
|
|
||||||
drop
|
|
||||||
drop
|
|
||||||
drop
|
|
||||||
)
|
|
||||||
)
|
|
||||||
"#,
|
|
||||||
);
|
|
||||||
|
|
||||||
let height = compute(0, &module).unwrap();
|
// let height = compute(0, &module).unwrap();
|
||||||
assert_eq!(height, 3 + ACTIVATION_FRAME_COST);
|
// assert_eq!(height, 2 + ACTIVATION_FRAME_COST);
|
||||||
}
|
// }
|
||||||
}
|
|
||||||
|
// #[test]
|
||||||
|
// fn call_indirect() {
|
||||||
|
// let module = parse_wat(
|
||||||
|
// r#"
|
||||||
|
// (module
|
||||||
|
// (table $ptr 1 1 funcref)
|
||||||
|
// (elem $ptr (i32.const 0) func 1)
|
||||||
|
// (func $main
|
||||||
|
// (call_indirect (i32.const 0))
|
||||||
|
// (call_indirect (i32.const 0))
|
||||||
|
// (call_indirect (i32.const 0))
|
||||||
|
// )
|
||||||
|
// (func $callee
|
||||||
|
// i64.const 42
|
||||||
|
// drop
|
||||||
|
// )
|
||||||
|
// )
|
||||||
|
// "#,
|
||||||
|
// );
|
||||||
|
|
||||||
|
// let height = compute(0, &module).unwrap();
|
||||||
|
// assert_eq!(height, 1 + ACTIVATION_FRAME_COST);
|
||||||
|
// }
|
||||||
|
|
||||||
|
// #[test]
|
||||||
|
// fn breaks() {
|
||||||
|
// let module = parse_wat(
|
||||||
|
// r#"
|
||||||
|
// (module
|
||||||
|
// (func $main
|
||||||
|
// block (result i32)
|
||||||
|
// block (result i32)
|
||||||
|
// i32.const 99
|
||||||
|
// br 1
|
||||||
|
// end
|
||||||
|
// end
|
||||||
|
// drop
|
||||||
|
// )
|
||||||
|
// )
|
||||||
|
// "#,
|
||||||
|
// );
|
||||||
|
|
||||||
|
// let height = compute(0, &module).unwrap();
|
||||||
|
// assert_eq!(height, 1 + ACTIVATION_FRAME_COST);
|
||||||
|
// }
|
||||||
|
|
||||||
|
// #[test]
|
||||||
|
// fn if_else_works() {
|
||||||
|
// let module = parse_wat(
|
||||||
|
// r#"
|
||||||
|
// (module
|
||||||
|
// (func $main
|
||||||
|
// i32.const 7
|
||||||
|
// i32.const 1
|
||||||
|
// if (result i32)
|
||||||
|
// i32.const 42
|
||||||
|
// else
|
||||||
|
// i32.const 99
|
||||||
|
// end
|
||||||
|
// i32.const 97
|
||||||
|
// drop
|
||||||
|
// drop
|
||||||
|
// drop
|
||||||
|
// )
|
||||||
|
// )
|
||||||
|
// "#,
|
||||||
|
// );
|
||||||
|
|
||||||
|
// let height = compute(0, &module).unwrap();
|
||||||
|
// assert_eq!(height, 3 + ACTIVATION_FRAME_COST);
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|||||||
+11
-23
@@ -6,6 +6,7 @@ use parity_wasm::{
|
|||||||
builder,
|
builder,
|
||||||
elements::{self, Instruction, Instructions, Type},
|
elements::{self, Instruction, Instructions, Type},
|
||||||
};
|
};
|
||||||
|
pub use max_height::StackHeightStats;
|
||||||
|
|
||||||
/// Macro to generate preamble and postamble.
|
/// Macro to generate preamble and postamble.
|
||||||
macro_rules! instrument_call {
|
macro_rules! instrument_call {
|
||||||
@@ -40,7 +41,7 @@ mod thunk;
|
|||||||
|
|
||||||
pub struct Context {
|
pub struct Context {
|
||||||
stack_height_global_idx: u32,
|
stack_height_global_idx: u32,
|
||||||
func_stack_costs: Vec<u32>,
|
func_stack_costs: Vec<StackHeightStats>,
|
||||||
stack_limit: u32,
|
stack_limit: u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -52,7 +53,11 @@ impl Context {
|
|||||||
|
|
||||||
/// Returns `stack_cost` for `func_idx`.
|
/// Returns `stack_cost` for `func_idx`.
|
||||||
fn stack_cost(&self, func_idx: u32) -> Option<u32> {
|
fn stack_cost(&self, func_idx: u32) -> Option<u32> {
|
||||||
self.func_stack_costs.get(func_idx as usize).cloned()
|
if let Some(stats) = self.func_stack_costs.get(func_idx as usize) {
|
||||||
|
Some(stats.total_cost)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns stack limit specified by the rules.
|
/// Returns stack limit specified by the rules.
|
||||||
@@ -154,7 +159,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> {
|
fn compute_stack_costs(module: &elements::Module) -> Result<Vec<StackHeightStats>, &'static str> {
|
||||||
let func_imports = module.import_count(elements::ImportCountType::Function);
|
let func_imports = module.import_count(elements::ImportCountType::Function);
|
||||||
|
|
||||||
// TODO: optimize!
|
// TODO: optimize!
|
||||||
@@ -162,7 +167,7 @@ fn compute_stack_costs(module: &elements::Module) -> Result<Vec<u32>, &'static s
|
|||||||
.map(|func_idx| {
|
.map(|func_idx| {
|
||||||
if func_idx < func_imports {
|
if func_idx < func_imports {
|
||||||
// We can't calculate stack_cost of the import functions.
|
// We can't calculate stack_cost of the import functions.
|
||||||
Ok(0)
|
Ok(Default::default())
|
||||||
} else {
|
} else {
|
||||||
compute_stack_cost(func_idx as u32, module)
|
compute_stack_cost(func_idx as u32, module)
|
||||||
}
|
}
|
||||||
@@ -173,7 +178,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<StackHeightStats, &'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;
|
||||||
@@ -181,24 +186,7 @@ fn compute_stack_cost(func_idx: u32, module: &elements::Module) -> Result<u32, &
|
|||||||
.checked_sub(func_imports)
|
.checked_sub(func_imports)
|
||||||
.ok_or("This should be a index of a defined function")?;
|
.ok_or("This should be a index of a defined function")?;
|
||||||
|
|
||||||
let code_section =
|
max_height::compute(defined_func_idx, module)
|
||||||
module.code_section().ok_or("Due to validation code section should exists")?;
|
|
||||||
let body = &code_section
|
|
||||||
.bodies()
|
|
||||||
.get(defined_func_idx as usize)
|
|
||||||
.ok_or("Function body is out of bounds")?;
|
|
||||||
|
|
||||||
let mut locals_count: u32 = 0;
|
|
||||||
for local_group in body.locals() {
|
|
||||||
locals_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)?;
|
|
||||||
|
|
||||||
locals_count
|
|
||||||
.checked_add(max_stack_height)
|
|
||||||
.ok_or("Overflow in adding locals_count and max_stack_height")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn instrument_functions(
|
fn instrument_functions(
|
||||||
|
|||||||
Reference in New Issue
Block a user