-
Notifications
You must be signed in to change notification settings - Fork 824
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
16 changed files
with
563 additions
and
23 deletions.
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -8,4 +8,15 @@ authors = ["The Wasmer Engineering Team <[email protected]>"] | |
edition = "2018" | ||
|
||
[dependencies] | ||
wasmer-runtime-core = { path = "../runtime-core", version = "0.4.2" } | ||
wasmer-runtime-core = { path = "../runtime-core" } | ||
wasmer-clif-backend = { path = "../clif-backend", version = "0.4.2" } | ||
wasmer-llvm-backend = { path = "../llvm-backend", version = "0.4.2", optional = true } | ||
wasmer-singlepass-backend = { path = "../singlepass-backend", version = "0.4.2", optional = true } | ||
|
||
[dev-dependencies] | ||
wabt = "0.7.4" | ||
|
||
[features] | ||
clif = [] | ||
llvm = ["wasmer-llvm-backend"] | ||
singlepass = ["wasmer-singlepass-backend"] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,4 @@ | ||
#![deny(unused_imports, unused_variables, unused_unsafe, unreachable_patterns)] | ||
|
||
pub mod call_trace; | ||
pub mod metering; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,281 @@ | ||
use wasmer_runtime_core::{ | ||
codegen::{Event, EventSink, FunctionMiddleware, InternalEvent}, | ||
module::ModuleInfo, | ||
vm::InternalField, | ||
wasmparser::{Operator, Type as WpType}, | ||
Instance, | ||
}; | ||
|
||
static INTERNAL_FIELD: InternalField = InternalField::allocate(); | ||
|
||
/// Metering is a compiler middleware that calculates the cost of WebAssembly instructions at compile | ||
/// time and will count the cost of executed instructions at runtime. Within the Metering functionality, | ||
/// this instruction cost is called `points`. | ||
/// | ||
/// The Metering struct takes a `limit` parameter which is the maximum number of points which can be | ||
/// used by an instance during a function call. If this limit is exceeded, the function call will | ||
/// trap. Each instance has a `points_used` field which can be used to track points used during | ||
/// a function call and should be set back to zero after a function call. | ||
/// | ||
/// Each compiler backend with Metering enabled should produce the same cost used at runtime for | ||
/// the same function calls so we can say that the metering is deterministic. | ||
/// | ||
pub struct Metering { | ||
limit: u64, | ||
current_block: u64, | ||
} | ||
|
||
impl Metering { | ||
pub fn new(limit: u64) -> Metering { | ||
Metering { | ||
limit, | ||
current_block: 0, | ||
} | ||
} | ||
} | ||
|
||
#[derive(Copy, Clone, Debug)] | ||
pub struct ExecutionLimitExceededError; | ||
|
||
impl FunctionMiddleware for Metering { | ||
type Error = String; | ||
fn feed_event<'a, 'b: 'a>( | ||
&mut self, | ||
op: Event<'a, 'b>, | ||
_module_info: &ModuleInfo, | ||
sink: &mut EventSink<'a, 'b>, | ||
) -> Result<(), Self::Error> { | ||
match op { | ||
Event::Internal(InternalEvent::FunctionBegin(_)) => { | ||
self.current_block = 0; | ||
} | ||
Event::Wasm(&ref op) | Event::WasmOwned(ref op) => { | ||
self.current_block += 1; | ||
match *op { | ||
Operator::Loop { .. } | ||
| Operator::Block { .. } | ||
| Operator::End | ||
| Operator::If { .. } | ||
| Operator::Else | ||
| Operator::Unreachable | ||
| Operator::Br { .. } | ||
| Operator::BrTable { .. } | ||
| Operator::BrIf { .. } | ||
| Operator::Call { .. } | ||
| Operator::CallIndirect { .. } | ||
| Operator::Return => { | ||
sink.push(Event::Internal(InternalEvent::GetInternal( | ||
INTERNAL_FIELD.index() as _, | ||
))); | ||
sink.push(Event::WasmOwned(Operator::I64Const { | ||
value: self.current_block as i64, | ||
})); | ||
sink.push(Event::WasmOwned(Operator::I64Add)); | ||
sink.push(Event::Internal(InternalEvent::SetInternal( | ||
INTERNAL_FIELD.index() as _, | ||
))); | ||
self.current_block = 0; | ||
} | ||
_ => {} | ||
} | ||
match *op { | ||
Operator::Br { .. } | ||
| Operator::BrTable { .. } | ||
| Operator::BrIf { .. } | ||
| Operator::Call { .. } | ||
| Operator::CallIndirect { .. } => { | ||
sink.push(Event::Internal(InternalEvent::GetInternal( | ||
INTERNAL_FIELD.index() as _, | ||
))); | ||
sink.push(Event::WasmOwned(Operator::I64Const { | ||
value: self.limit as i64, | ||
})); | ||
sink.push(Event::WasmOwned(Operator::I64GeU)); | ||
sink.push(Event::WasmOwned(Operator::If { | ||
ty: WpType::EmptyBlockType, | ||
})); | ||
sink.push(Event::Internal(InternalEvent::Breakpoint(Box::new( | ||
move |ctx| unsafe { | ||
(ctx.throw)(Box::new(ExecutionLimitExceededError)); | ||
}, | ||
)))); | ||
sink.push(Event::WasmOwned(Operator::End)); | ||
} | ||
_ => {} | ||
} | ||
} | ||
_ => {} | ||
} | ||
sink.push(op); | ||
Ok(()) | ||
} | ||
} | ||
|
||
/// Returns the number of points used by a function call for metering | ||
pub fn get_points_used(instance: &Instance) -> u64 { | ||
instance.get_internal(&INTERNAL_FIELD) | ||
} | ||
|
||
/// Sets the value of points used | ||
pub fn set_points_used(instance: &mut Instance, value: u64) { | ||
instance.set_internal(&INTERNAL_FIELD, value); | ||
} | ||
|
||
#[cfg(all(test, feature = "singlepass"))] | ||
mod tests { | ||
use super::*; | ||
use wabt::wat2wasm; | ||
|
||
use wasmer_runtime_core::{backend::Compiler, compile_with, imports, Func}; | ||
|
||
#[cfg(feature = "llvm")] | ||
fn get_compiler(limit: u64) -> impl Compiler { | ||
use wasmer_llvm_backend::code::LLVMModuleCodeGenerator; | ||
use wasmer_runtime_core::codegen::{MiddlewareChain, StreamingCompiler}; | ||
let c: StreamingCompiler<LLVMModuleCodeGenerator, _, _, _, _> = | ||
StreamingCompiler::new(move || { | ||
let mut chain = MiddlewareChain::new(); | ||
chain.push(Metering::new(limit)); | ||
chain | ||
}); | ||
c | ||
} | ||
|
||
#[cfg(feature = "singlepass")] | ||
fn get_compiler(limit: u64) -> impl Compiler { | ||
use wasmer_runtime_core::codegen::{MiddlewareChain, StreamingCompiler}; | ||
use wasmer_singlepass_backend::ModuleCodeGenerator as SinglePassMCG; | ||
let c: StreamingCompiler<SinglePassMCG, _, _, _, _> = StreamingCompiler::new(move || { | ||
let mut chain = MiddlewareChain::new(); | ||
chain.push(Metering::new(limit)); | ||
chain | ||
}); | ||
c | ||
} | ||
|
||
#[cfg(not(any(feature = "llvm", feature = "clif", feature = "singlepass")))] | ||
fn get_compiler(_limit: u64) -> impl Compiler { | ||
panic!("compiler not specified, activate a compiler via features"); | ||
use wasmer_clif_backend::CraneliftCompiler; | ||
CraneliftCompiler::new() | ||
} | ||
|
||
#[cfg(feature = "clif")] | ||
fn get_compiler(_limit: u64) -> impl Compiler { | ||
panic!("cranelift does not implement metering"); | ||
use wasmer_clif_backend::CraneliftCompiler; | ||
CraneliftCompiler::new() | ||
} | ||
|
||
// Assemblyscript | ||
// export function add_to(x: i32, y: i32): i32 { | ||
// for(var i = 0; i < x; i++){ | ||
// if(i % 1 == 0){ | ||
// y += i; | ||
// } else { | ||
// y *= i | ||
// } | ||
// } | ||
// return y; | ||
// } | ||
static WAT: &'static str = r#" | ||
(module | ||
(type $t0 (func (param i32 i32) (result i32))) | ||
(type $t1 (func)) | ||
(func $add_to (export "add_to") (type $t0) (param $p0 i32) (param $p1 i32) (result i32) | ||
(local $l0 i32) | ||
block $B0 | ||
i32.const 0 | ||
set_local $l0 | ||
loop $L1 | ||
get_local $l0 | ||
get_local $p0 | ||
i32.lt_s | ||
i32.eqz | ||
br_if $B0 | ||
get_local $l0 | ||
i32.const 1 | ||
i32.rem_s | ||
i32.const 0 | ||
i32.eq | ||
if $I2 | ||
get_local $p1 | ||
get_local $l0 | ||
i32.add | ||
set_local $p1 | ||
else | ||
get_local $p1 | ||
get_local $l0 | ||
i32.mul | ||
set_local $p1 | ||
end | ||
get_local $l0 | ||
i32.const 1 | ||
i32.add | ||
set_local $l0 | ||
br $L1 | ||
unreachable | ||
end | ||
unreachable | ||
end | ||
get_local $p1) | ||
(func $f1 (type $t1)) | ||
(table $table (export "table") 1 anyfunc) | ||
(memory $memory (export "memory") 0) | ||
(global $g0 i32 (i32.const 8)) | ||
(elem (i32.const 0) $f1)) | ||
"#; | ||
|
||
#[test] | ||
fn test_points_reduced_after_call() { | ||
let wasm_binary = wat2wasm(WAT).unwrap(); | ||
|
||
let limit = 100u64; | ||
|
||
let module = compile_with(&wasm_binary, &get_compiler(limit)).unwrap(); | ||
|
||
let import_object = imports! {}; | ||
let mut instance = module.instantiate(&import_object).unwrap(); | ||
|
||
set_points_used(&mut instance, 0u64); | ||
|
||
let add_to: Func<(i32, i32), i32> = instance.func("add_to").unwrap(); | ||
let value = add_to.call(3, 4).unwrap(); | ||
|
||
// verify it returns the correct value | ||
assert_eq!(value, 7); | ||
|
||
// verify is uses the correct number of points | ||
assert_eq!(get_points_used(&instance), 74); | ||
} | ||
|
||
#[test] | ||
fn test_traps_after_costly_call() { | ||
use wasmer_runtime_core::error::RuntimeError; | ||
let wasm_binary = wat2wasm(WAT).unwrap(); | ||
|
||
let limit = 100u64; | ||
|
||
let module = compile_with(&wasm_binary, &get_compiler(limit)).unwrap(); | ||
|
||
let import_object = imports! {}; | ||
let mut instance = module.instantiate(&import_object).unwrap(); | ||
|
||
set_points_used(&mut instance, 0u64); | ||
|
||
let add_to: Func<(i32, i32), i32> = instance.func("add_to").unwrap(); | ||
let result = add_to.call(10_000_000, 4); | ||
|
||
let err = result.unwrap_err(); | ||
match err { | ||
RuntimeError::Error { data } => { | ||
assert!(data.downcast_ref::<ExecutionLimitExceededError>().is_some()); | ||
} | ||
_ => unreachable!(), | ||
} | ||
|
||
// verify is uses the correct number of points | ||
assert_eq!(get_points_used(&instance), 109); // Used points will be slightly more than `limit` because of the way we do gas checking. | ||
} | ||
|
||
} |
Oops, something went wrong.