Skip to content

Commit

Permalink
Try #450:
Browse files Browse the repository at this point in the history
  • Loading branch information
bors[bot] committed Jun 4, 2019
2 parents df486b7 + 0867208 commit bd1f598
Show file tree
Hide file tree
Showing 16 changed files with 553 additions and 23 deletions.
4 changes: 4 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 4 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -48,18 +48,21 @@ do-install:

test:
# We use one thread so the emscripten stdouts doesn't collide
cargo test --all --exclude wasmer-runtime-c-api --exclude wasmer-emscripten --exclude wasmer-spectests --exclude wasmer-singlepass-backend --exclude wasmer-wasi -- $(runargs)
cargo test --all --exclude wasmer-runtime-c-api --exclude wasmer-emscripten --exclude wasmer-spectests --exclude wasmer-singlepass-backend --exclude wasmer-wasi --exclude wasmer-middleware-common -- $(runargs)
# cargo test --all --exclude wasmer-emscripten -- --test-threads=1 $(runargs)
cargo test --manifest-path lib/spectests/Cargo.toml --features clif
cargo test --manifest-path lib/middleware-common/Cargo.toml --features clif
@if [ ! -z "${CIRCLE_JOB}" ]; then rm -f /home/circleci/project/target/debug/deps/libcranelift_wasm* && rm -f /Users/distiller/project/target/debug/deps/libcranelift_wasm*; fi;
cargo test --manifest-path lib/spectests/Cargo.toml --features llvm
cargo test --manifest-path lib/runtime/Cargo.toml --features llvm
cargo test --manifest-path lib/middleware-common/Cargo.toml --features llvm
cargo build -p wasmer-runtime-c-api
cargo test -p wasmer-runtime-c-api -- --nocapture

test-singlepass:
cargo test --manifest-path lib/spectests/Cargo.toml --features singlepass
cargo test --manifest-path lib/runtime/Cargo.toml --features singlepass
cargo test --manifest-path lib/middleware-common/Cargo.toml --features singlepass

test-emscripten-llvm:
cargo test --manifest-path lib/emscripten/Cargo.toml --features llvm -- --test-threads=1 $(runargs)
Expand Down
1 change: 1 addition & 0 deletions lib/clif-backend/src/code.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1122,6 +1122,7 @@ impl FunctionCodeGenerator<CodegenError> for CraneliftFunctionCodeGenerator {
fn feed_event(&mut self, event: Event, _module_info: &ModuleInfo) -> Result<(), CodegenError> {
let op = match event {
Event::Wasm(x) => x,
Event::WasmOwned(ref x) => x,
Event::Internal(_x) => {
return Ok(());
}
Expand Down
1 change: 1 addition & 0 deletions lib/llvm-backend/src/code.rs
Original file line number Diff line number Diff line change
Expand Up @@ -475,6 +475,7 @@ impl FunctionCodeGenerator<CodegenError> for LLVMFunctionCodeGenerator {
Event::Internal(_x) => {
return Ok(());
}
Event::WasmOwned(ref x) => x,
};

let mut state = &mut self.state;
Expand Down
4 changes: 4 additions & 0 deletions lib/llvm-backend/src/intrinsics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,7 @@ impl Intrinsics {
let stack_lower_bound_ty = i8_ty;
let memory_base_ty = i8_ty;
let memory_bound_ty = void_ty;
let internals_ty = i64_ty;
let local_function_ty = i8_ptr_ty;

let anyfunc_ty = context.struct_type(
Expand Down Expand Up @@ -218,6 +219,9 @@ impl Intrinsics {
memory_bound_ty
.ptr_type(AddressSpace::Generic)
.as_basic_type_enum(),
internals_ty
.ptr_type(AddressSpace::Generic)
.as_basic_type_enum(),
local_function_ty
.ptr_type(AddressSpace::Generic)
.as_basic_type_enum(),
Expand Down
13 changes: 12 additions & 1 deletion lib/middleware-common/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"]
1 change: 1 addition & 0 deletions lib/middleware-common/src/lib.rs
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;
275 changes: 275 additions & 0 deletions lib/middleware-common/src/metering.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,275 @@
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,
}
}
}

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| {
eprintln!("execution limit reached");
unsafe {
(ctx.throw)();
}
},
))));
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() {
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);

// verify it errors
assert_eq!(result.is_err(), true); // TODO assert that the trap is caused by PointsExausted

// 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.
}

}
Loading

0 comments on commit bd1f598

Please sign in to comment.