Backward compatibility and tracing

This commit is contained in:
Dmitry Sinyavin
2022-07-27 13:45:52 +02:00
parent 8a552c033c
commit 6bf31c0331
4 changed files with 119 additions and 43 deletions
+4
View File
@@ -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"]
+3 -1
View File
@@ -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};
pub use stack_limiter::{
compute_stack_cost, compute_stack_height_weight, inject as inject_stack_limiter,
};
+97 -41
View File
@@ -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<ValueType>,
/// 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<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.
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<u32, &'static str> {
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<u32, &'static
.map(|g| g.global_type().content_type())
.collect()
} else {
vec![]
Vec::new()
};
let locals: Vec<ValueType> = 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<u32, &'static
start_height: 0,
});
loop {
if pc >= 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<u32, &'static
let height = stack.height();
let end_result = if let BlockType::Value(vt) = *ty { Some(vt) } else { None };
stack.push_frame(Frame {
is_polymorphic: false,
is_polymorphic: stack.frame(0)?.is_polymorphic, /* Block inside unreachable
* code is
* unreachable */
result_type: end_result,
branch_type: if let Loop(_) = *opcode { None } else { end_result },
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 {
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 => {
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,
// save the new weight.
// However, we don't increase maximal value in unreachable code.
if stack.weight() > 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));
}
}
+15 -1
View File
@@ -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")?;
}
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
.checked_add(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(
ctx: &mut Context,
module: &mut elements::Module,