mirror of
https://github.com/pezkuwichain/wasm-instrument.git
synced 2026-06-13 11:41:10 +00:00
Backward compatibility and tracing
This commit is contained in:
@@ -22,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"
|
||||||
@@ -36,3 +39,4 @@ wasmprinter = "0.2"
|
|||||||
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
@@ -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::{compute_stack_cost, compute_stack_costs, inject as inject_stack_limiter};
|
pub use stack_limiter::{
|
||||||
|
compute_stack_cost, compute_stack_height_weight, inject as inject_stack_limiter,
|
||||||
|
};
|
||||||
|
|||||||
@@ -1,10 +1,31 @@
|
|||||||
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, ValueType};
|
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
|
||||||
|
// 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.
|
/// Control stack frame.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
struct Frame {
|
struct Frame {
|
||||||
@@ -13,11 +34,11 @@ struct Frame {
|
|||||||
is_polymorphic: bool,
|
is_polymorphic: bool,
|
||||||
|
|
||||||
/// Type of value which will be pushed after exiting
|
/// 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<ValueType>,
|
result_type: Option<ValueType>,
|
||||||
|
|
||||||
/// Type of value which should be poped upon a branch to
|
/// 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
|
/// 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.
|
||||||
@@ -69,24 +90,35 @@ 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: usize) {
|
fn trunc(&mut self, new_height: usize) {
|
||||||
|
trace!(" Truncate value stack to {}", new_height);
|
||||||
self.values.truncate(new_height);
|
self.values.truncate(new_height);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Push a value into the value stack.
|
/// Push a value into the value stack.
|
||||||
fn push_value(&mut self, value: ValueType) -> Result<(), &'static str> {
|
fn push_value(&mut self, value: ValueType) -> Result<(), &'static str> {
|
||||||
|
trace!(" Push {:?} to value stack", value);
|
||||||
self.values.push(value);
|
self.values.push(value);
|
||||||
|
if self.values.len() >= u32::MAX as usize {
|
||||||
|
return Err("stack overflow")
|
||||||
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -105,7 +137,9 @@ impl Stack {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if self.height() > 0 {
|
if self.height() > 0 {
|
||||||
Ok(self.values.pop())
|
let vt = self.values.pop();
|
||||||
|
trace!("Pop {:?} from value stack", vt);
|
||||||
|
Ok(vt)
|
||||||
} else {
|
} else {
|
||||||
Err("trying to pop more values than pushed")
|
Err("trying to pop more values than pushed")
|
||||||
}
|
}
|
||||||
@@ -114,13 +148,13 @@ impl Stack {
|
|||||||
|
|
||||||
fn value_cost(val: ValueType) -> u32 {
|
fn value_cost(val: ValueType) -> u32 {
|
||||||
match val {
|
match val {
|
||||||
ValueType::I32 | ValueType::F32 => 1,
|
ValueType::I32 | ValueType::F32 => 4,
|
||||||
ValueType::I64 | ValueType::F64 => 2,
|
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")?;
|
||||||
@@ -151,19 +185,15 @@ pub fn compute(func_idx: u32, module: &elements::Module) -> Result<u32, &'static
|
|||||||
.map(|g| g.global_type().content_type())
|
.map(|g| g.global_type().content_type())
|
||||||
.collect()
|
.collect()
|
||||||
} else {
|
} else {
|
||||||
vec![]
|
Vec::new()
|
||||||
};
|
};
|
||||||
|
|
||||||
let locals: Vec<ValueType> = func_signature
|
let mut locals = func_signature.params().to_vec();
|
||||||
.params()
|
locals.extend(body.locals().iter().flat_map(|l| vec![l.value_type(); l.count() as usize]));
|
||||||
.iter()
|
|
||||||
.cloned()
|
|
||||||
.chain(body.locals().iter().flat_map(|l| vec![l.value_type(); l.count() as usize]))
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
let mut stack = Stack::new();
|
let mut stack = Stack::new();
|
||||||
let mut max_weight: 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.
|
||||||
@@ -179,12 +209,8 @@ pub fn compute(func_idx: u32, module: &elements::Module) -> Result<u32, &'static
|
|||||||
start_height: 0,
|
start_height: 0,
|
||||||
});
|
});
|
||||||
|
|
||||||
loop {
|
for opcode in instructions.elements() {
|
||||||
if pc >= instructions.elements().len() {
|
trace!("Processing opcode {:?}", opcode);
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
let opcode = &instructions.elements()[pc];
|
|
||||||
|
|
||||||
match opcode {
|
match opcode {
|
||||||
Nop => {},
|
Nop => {},
|
||||||
@@ -195,7 +221,9 @@ pub fn compute(func_idx: u32, module: &elements::Module) -> Result<u32, &'static
|
|||||||
let height = stack.height();
|
let height = stack.height();
|
||||||
let end_result = if let BlockType::Value(vt) = *ty { Some(vt) } else { None };
|
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
|
||||||
|
* code is
|
||||||
|
* unreachable */
|
||||||
result_type: end_result,
|
result_type: end_result,
|
||||||
branch_type: if let Loop(_) = *opcode { None } else { end_result },
|
branch_type: if let Loop(_) = *opcode { None } else { end_result },
|
||||||
start_height: height,
|
start_height: height,
|
||||||
@@ -211,6 +239,9 @@ pub fn compute(func_idx: u32, module: &elements::Module) -> Result<u32, &'static
|
|||||||
if let Some(vt) = frame.result_type {
|
if let Some(vt) = frame.result_type {
|
||||||
stack.push_value(vt)?;
|
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()?;
|
||||||
@@ -487,14 +518,36 @@ pub fn compute(func_idx: u32, module: &elements::Module) -> Result<u32, &'static
|
|||||||
// If current value stack is heavier than maximal weight observed so far,
|
// If current value stack is heavier than maximal weight observed so far,
|
||||||
// save the new weight.
|
// save the new weight.
|
||||||
// However, we don't increase maximal value in unreachable code.
|
// However, we don't increase maximal value in unreachable code.
|
||||||
if stack.weight() > max_weight && !stack.frame(0)?.is_polymorphic {
|
if !stack.frame(0)?.is_polymorphic {
|
||||||
max_weight = stack.weight();
|
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)]
|
#[cfg(test)]
|
||||||
@@ -502,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")
|
||||||
@@ -524,8 +580,8 @@ mod tests {
|
|||||||
"#,
|
"#,
|
||||||
);
|
);
|
||||||
|
|
||||||
let weight = compute(0, &module).unwrap();
|
let res = compute(0, &module).unwrap();
|
||||||
assert_eq!(weight, 3);
|
assert_eq!(res, (ACTIVATION_FRAME_HEIGHT + 3, ACTIVATION_FRAME_WEIGHT + 12));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -541,8 +597,8 @@ mod tests {
|
|||||||
"#,
|
"#,
|
||||||
);
|
);
|
||||||
|
|
||||||
let weight = compute(0, &module).unwrap();
|
let res = compute(0, &module).unwrap();
|
||||||
assert_eq!(weight, 2);
|
assert_eq!(res, (ACTIVATION_FRAME_HEIGHT + 1, ACTIVATION_FRAME_WEIGHT + 8));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -559,8 +615,8 @@ mod tests {
|
|||||||
"#,
|
"#,
|
||||||
);
|
);
|
||||||
|
|
||||||
let weight = compute(0, &module).unwrap();
|
let res = compute(0, &module).unwrap();
|
||||||
assert_eq!(weight, 0);
|
assert_eq!(res, (ACTIVATION_FRAME_HEIGHT, ACTIVATION_FRAME_WEIGHT));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -588,8 +644,8 @@ mod tests {
|
|||||||
"#,
|
"#,
|
||||||
);
|
);
|
||||||
|
|
||||||
let weight = compute(0, &module).unwrap();
|
let res = compute(0, &module).unwrap();
|
||||||
assert_eq!(weight, 2);
|
assert_eq!(res, (ACTIVATION_FRAME_HEIGHT + 2, ACTIVATION_FRAME_WEIGHT + 8));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -612,8 +668,8 @@ mod tests {
|
|||||||
"#,
|
"#,
|
||||||
);
|
);
|
||||||
|
|
||||||
let weight = compute(0, &module).unwrap();
|
let res = compute(0, &module).unwrap();
|
||||||
assert_eq!(weight, 1);
|
assert_eq!(res, (ACTIVATION_FRAME_HEIGHT + 1, ACTIVATION_FRAME_WEIGHT + 4));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -634,8 +690,8 @@ mod tests {
|
|||||||
"#,
|
"#,
|
||||||
);
|
);
|
||||||
|
|
||||||
let weight = compute(0, &module).unwrap();
|
let res = compute(0, &module).unwrap();
|
||||||
assert_eq!(weight, 1);
|
assert_eq!(res, (ACTIVATION_FRAME_HEIGHT + 1, ACTIVATION_FRAME_WEIGHT + 4));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -660,7 +716,7 @@ mod tests {
|
|||||||
"#,
|
"#,
|
||||||
);
|
);
|
||||||
|
|
||||||
let weight = compute(0, &module).unwrap();
|
let res = compute(0, &module).unwrap();
|
||||||
assert_eq!(weight, 3);
|
assert_eq!(res, (ACTIVATION_FRAME_HEIGHT + 3, ACTIVATION_FRAME_WEIGHT + 12));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -194,13 +194,27 @@ pub fn compute_stack_cost(func_idx: u32, module: &elements::Module) -> Result<u3
|
|||||||
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,
|
||||||
|
|||||||
Reference in New Issue
Block a user