diff --git a/Cargo.lock b/Cargo.lock index d2c1458525d..60052424362 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1460,7 +1460,12 @@ dependencies = [ name = "wasmer-middleware-common" version = "0.4.2" dependencies = [ + "criterion 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", + "wabt 0.7.4 (registry+https://github.com/rust-lang/crates.io-index)", + "wasmer-clif-backend 0.4.2", + "wasmer-llvm-backend 0.4.2", "wasmer-runtime-core 0.4.2", + "wasmer-singlepass-backend 0.4.2", ] [[package]] diff --git a/Makefile b/Makefile index 13822cce00d..463e7f8e168 100644 --- a/Makefile +++ b/Makefile @@ -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) diff --git a/lib/clif-backend/src/code.rs b/lib/clif-backend/src/code.rs index 3a4b48016ec..576f513e1d9 100644 --- a/lib/clif-backend/src/code.rs +++ b/lib/clif-backend/src/code.rs @@ -1122,6 +1122,7 @@ impl FunctionCodeGenerator 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(()); } diff --git a/lib/llvm-backend/src/code.rs b/lib/llvm-backend/src/code.rs index c5db4cd2482..7e76698a91e 100644 --- a/lib/llvm-backend/src/code.rs +++ b/lib/llvm-backend/src/code.rs @@ -475,6 +475,7 @@ impl FunctionCodeGenerator for LLVMFunctionCodeGenerator { Event::Internal(_x) => { return Ok(()); } + Event::WasmOwned(ref x) => x, }; let mut state = &mut self.state; diff --git a/lib/llvm-backend/src/intrinsics.rs b/lib/llvm-backend/src/intrinsics.rs index ebee5a3882d..0e00ff42168 100644 --- a/lib/llvm-backend/src/intrinsics.rs +++ b/lib/llvm-backend/src/intrinsics.rs @@ -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( @@ -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(), diff --git a/lib/middleware-common/Cargo.toml b/lib/middleware-common/Cargo.toml index 4267b240c99..d6f7c2c4b30 100644 --- a/lib/middleware-common/Cargo.toml +++ b/lib/middleware-common/Cargo.toml @@ -8,4 +8,20 @@ authors = ["The Wasmer Engineering Team "] 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" +criterion = "0.2" + +[features] +clif = [] +llvm = ["wasmer-llvm-backend"] +singlepass = ["wasmer-singlepass-backend"] + +[[bench]] +name = "metering_benchmark" +harness = false \ No newline at end of file diff --git a/lib/middleware-common/benches/metering_benchmark.rs b/lib/middleware-common/benches/metering_benchmark.rs new file mode 100644 index 00000000000..20759ed0109 --- /dev/null +++ b/lib/middleware-common/benches/metering_benchmark.rs @@ -0,0 +1,230 @@ +#[macro_use] +extern crate criterion; + +use criterion::black_box; +use criterion::{Benchmark, Criterion}; + +use wabt::wat2wasm; + +use wasmer_middleware_common::metering::Metering; +use wasmer_runtime_core::vm::Ctx; +use wasmer_runtime_core::{backend::Compiler, compile_with, imports, Func}; + +//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)) + "#; + +static WAT_GAS: &'static str = r#" + (module + (type $t0 (func (param i32 i32) (result i32))) + (type $t1 (func)) + (type $t2 (func (param i32))) + (import "env" "gas" (func $env.gas (type $t2))) + (func $add_to (type $t0) (param $p0 i32) (param $p1 i32) (result i32) + (local $l0 i32) + i32.const 3 + call $env.gas + block $B0 + i32.const 5 + call $env.gas + i32.const 0 + set_local $l0 + loop $L1 + i32.const 18 + call $env.gas + 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 + i32.const 5 + call $env.gas + get_local $p1 + get_local $l0 + i32.add + set_local $p1 + else + i32.const 5 + call $env.gas + 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 $f2 (type $t1) + i32.const 1 + call $env.gas) + (table $table 1 anyfunc) + (memory $memory 0) + (global $g0 i32 (i32.const 8)) + (export "memory" (memory 0)) + (export "table" (table 0)) + (export "add_to" (func $add_to)) + (elem (i32.const 0) $f2)) + "#; + +#[cfg(feature = "llvm")] +fn get_compiler(limit: u64, metering: bool) -> impl Compiler { + use wasmer_llvm_backend::code::LLVMModuleCodeGenerator; + use wasmer_runtime_core::codegen::{MiddlewareChain, StreamingCompiler}; + let c: StreamingCompiler = + StreamingCompiler::new(move || { + let mut chain = MiddlewareChain::new(); + if metering { + chain.push(Metering::new(limit)); + } + chain + }); + + c +} + +#[cfg(feature = "singlepass")] +fn get_compiler(limit: u64, metering: bool) -> impl Compiler { + use wasmer_runtime_core::codegen::{MiddlewareChain, StreamingCompiler}; + use wasmer_singlepass_backend::ModuleCodeGenerator as SinglePassMCG; + let c: StreamingCompiler = StreamingCompiler::new(move || { + let mut chain = MiddlewareChain::new(); + if metering { + chain.push(Metering::new(limit)); + } + chain + }); + c +} + +#[cfg(not(any(feature = "llvm", feature = "clif", feature = "singlepass")))] +fn get_compiler(_limit: u64, metering: bool) -> 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, metering: bool) -> impl Compiler { + panic!("cranelift does not implement metering"); + use wasmer_clif_backend::CraneliftCompiler; + CraneliftCompiler::new() +} + +fn gas(ctx: &mut Ctx, gas_amount: u32) { + use wasmer_middleware_common::metering; + let used = metering::get_points_used_ctx(ctx); + metering::set_points_used_ctx(ctx, used + u64::from(gas_amount)); + () +} + +fn bench_metering(c: &mut Criterion) { + use wasmer_middleware_common::metering; + + c.bench( + "Meter", + Benchmark::new("No Metering", |b| { + let compiler = get_compiler(0, false); + let wasm_binary = wat2wasm(WAT).unwrap(); + let module = compile_with(&wasm_binary, &compiler).unwrap(); + let import_object = imports! {}; + let mut instance = module.instantiate(&import_object).unwrap(); + let add_to: Func<(i32, i32), i32> = instance.func("add_to").unwrap(); + b.iter(|| black_box(add_to.call(100, 4))) + }) + .with_function("Gas Metering", |b| { + let compiler = get_compiler(0, false); + let gas_wasm_binary = wat2wasm(WAT_GAS).unwrap(); + let gas_module = compile_with(&gas_wasm_binary, &compiler).unwrap(); + let gas_import_object = imports! { + "env" => { + "gas" => Func::new(gas), + }, + }; + let mut gas_instance = gas_module.instantiate(&gas_import_object).unwrap(); + let gas_add_to: Func<(i32, i32), i32> = gas_instance.func("add_to").unwrap(); + b.iter(|| black_box(gas_add_to.call(100, 4))) + }) + .with_function("Built-in Metering", |b| { + let metering_compiler = get_compiler(std::u64::MAX, true); + let wasm_binary = wat2wasm(WAT).unwrap(); + let metering_module = compile_with(&wasm_binary, &metering_compiler).unwrap(); + let metering_import_object = imports! {}; + let mut metering_instance = metering_module + .instantiate(&metering_import_object) + .unwrap(); + metering::set_points_used(&mut metering_instance, 0u64); + let metering_add_to: Func<(i32, i32), i32> = metering_instance.func("add_to").unwrap(); + b.iter(|| black_box(metering_add_to.call(100, 4))) + }), + ); +} + +criterion_group!(benches, bench_metering); +criterion_main!(benches); diff --git a/lib/middleware-common/src/lib.rs b/lib/middleware-common/src/lib.rs index e09d9ee03b7..41699c9e705 100644 --- a/lib/middleware-common/src/lib.rs +++ b/lib/middleware-common/src/lib.rs @@ -1,3 +1,4 @@ #![deny(unused_imports, unused_variables, unused_unsafe, unreachable_patterns)] pub mod call_trace; +pub mod metering; diff --git a/lib/middleware-common/src/metering.rs b/lib/middleware-common/src/metering.rs new file mode 100644 index 00000000000..dd5b856b5dc --- /dev/null +++ b/lib/middleware-common/src/metering.rs @@ -0,0 +1,291 @@ +use wasmer_runtime_core::{ + codegen::{Event, EventSink, FunctionMiddleware, InternalEvent}, + module::ModuleInfo, + vm::{Ctx, 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 an Instance. +pub fn get_points_used(instance: &Instance) -> u64 { + instance.get_internal(&INTERNAL_FIELD) +} + +/// Sets the number of points used by an Instance. +pub fn set_points_used(instance: &mut Instance, value: u64) { + instance.set_internal(&INTERNAL_FIELD, value); +} + +/// Returns the number of points used in a Ctx. +pub fn get_points_used_ctx(ctx: &Ctx) -> u64 { + ctx.get_internal(&INTERNAL_FIELD) +} + +/// Sets the number of points used in a Ctx. +pub fn set_points_used_ctx(ctx: &mut Ctx, value: u64) { + ctx.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 = + 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 = 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::().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. + } + +} diff --git a/lib/runtime-core/src/backing.rs b/lib/runtime-core/src/backing.rs index 821ff379f4d..2078837b052 100644 --- a/lib/runtime-core/src/backing.rs +++ b/lib/runtime-core/src/backing.rs @@ -15,7 +15,17 @@ use crate::{ }, vm, }; -use std::slice; +use std::{fmt::Debug, slice}; + +pub const INTERNALS_SIZE: usize = 256; + +pub(crate) struct Internals(pub(crate) [u64; INTERNALS_SIZE]); + +impl Debug for Internals { + fn fmt(&self, formatter: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { + write!(formatter, "Internals({:?})", &self.0[..]) + } +} /// The `LocalBacking` "owns" the memory used by all the local resources of an Instance. /// That is, local memories, tables, and globals (as well as some additional @@ -40,6 +50,8 @@ pub struct LocalBacking { /// as well) are subject to change. pub(crate) dynamic_sigindices: BoxedMap, pub(crate) local_functions: BoxedMap, + + pub(crate) internals: Internals, } impl LocalBacking { @@ -66,6 +78,8 @@ impl LocalBacking { dynamic_sigindices, local_functions, + + internals: Internals([0; INTERNALS_SIZE]), } } diff --git a/lib/runtime-core/src/codegen.rs b/lib/runtime-core/src/codegen.rs index b89c060f515..4ff0c25cb80 100644 --- a/lib/runtime-core/src/codegen.rs +++ b/lib/runtime-core/src/codegen.rs @@ -8,6 +8,7 @@ use crate::{ types::{FuncIndex, FuncSig, SigIndex}, }; use smallvec::SmallVec; +use std::any::Any; use std::fmt; use std::fmt::Debug; use std::marker::PhantomData; @@ -19,6 +20,7 @@ use wasmparser::{Operator, Type as WpType}; pub enum Event<'a, 'b> { Internal(InternalEvent), Wasm(&'b Operator<'a>), + WasmOwned(Operator<'a>), } pub enum InternalEvent { @@ -41,7 +43,9 @@ impl fmt::Debug for InternalEvent { } } -pub struct BkptInfo {} +pub struct BkptInfo { + pub throw: unsafe fn(Box) -> !, +} pub trait ModuleCodeGenerator, RM: RunnableModule, E: Debug> { /// Creates a new module code generator. diff --git a/lib/runtime-core/src/instance.rs b/lib/runtime-core/src/instance.rs index 69a8884c3af..cf09b48e75d 100644 --- a/lib/runtime-core/src/instance.rs +++ b/lib/runtime-core/src/instance.rs @@ -13,7 +13,7 @@ use crate::{ table::Table, typed_func::{Func, Wasm, WasmTrapInfo, WasmTypeList}, types::{FuncIndex, FuncSig, GlobalIndex, LocalOrImport, MemoryIndex, TableIndex, Type, Value}, - vm, + vm::{self, InternalField}, }; use smallvec::{smallvec, SmallVec}; use std::{mem, ptr::NonNull, sync::Arc}; @@ -372,6 +372,14 @@ impl Instance { pub fn module(&self) -> Module { Module::new(Arc::clone(&self.module)) } + + pub fn get_internal(&self, field: &InternalField) -> u64 { + self.inner.backing.internals.0[field.index()] + } + + pub fn set_internal(&mut self, field: &InternalField, value: u64) { + self.inner.backing.internals.0[field.index()] = value; + } } impl InstanceInner { diff --git a/lib/runtime-core/src/lib.rs b/lib/runtime-core/src/lib.rs index 662f5c970fd..d7e56e7df93 100644 --- a/lib/runtime-core/src/lib.rs +++ b/lib/runtime-core/src/lib.rs @@ -57,6 +57,8 @@ pub use self::module::Module; pub use self::typed_func::Func; use std::sync::Arc; +pub use wasmparser; + use self::cache::{Artifact, Error as CacheError}; pub mod prelude { diff --git a/lib/runtime-core/src/vm.rs b/lib/runtime-core/src/vm.rs index 323e40c219b..a845514491e 100644 --- a/lib/runtime-core/src/vm.rs +++ b/lib/runtime-core/src/vm.rs @@ -1,4 +1,4 @@ -pub use crate::backing::{ImportBacking, LocalBacking}; +pub use crate::backing::{ImportBacking, LocalBacking, INTERNALS_SIZE}; use crate::{ memory::{Memory, MemoryType}, module::{ModuleInfo, ModuleInner}, @@ -6,7 +6,13 @@ use crate::{ types::{LocalOrImport, MemoryIndex}, vmcalls, }; -use std::{ffi::c_void, mem, ptr}; +use std::{ + cell::UnsafeCell, + ffi::c_void, + mem, ptr, + sync::atomic::{AtomicUsize, Ordering}, + sync::Once, +}; use hashbrown::HashMap; @@ -92,6 +98,43 @@ pub struct InternalCtx { pub memory_base: *mut u8, pub memory_bound: usize, + + pub internals: *mut [u64; INTERNALS_SIZE], // TODO: Make this dynamic? +} + +static INTERNAL_FIELDS: AtomicUsize = AtomicUsize::new(0); + +pub struct InternalField { + init: Once, + inner: UnsafeCell, +} + +unsafe impl Send for InternalField {} +unsafe impl Sync for InternalField {} + +impl InternalField { + pub const fn allocate() -> InternalField { + InternalField { + init: Once::new(), + inner: UnsafeCell::new(::std::usize::MAX), + } + } + + pub fn index(&self) -> usize { + let inner: *mut usize = self.inner.get(); + self.init.call_once(|| { + let idx = INTERNAL_FIELDS.fetch_add(1, Ordering::SeqCst); + if idx >= INTERNALS_SIZE { + INTERNAL_FIELDS.fetch_sub(1, Ordering::SeqCst); + panic!("at most {} internal fields are supported", INTERNALS_SIZE); + } else { + unsafe { + *inner = idx; + } + } + }); + unsafe { *inner } + } } #[repr(C)] @@ -200,6 +243,8 @@ impl Ctx { memory_base: mem_base, memory_bound: mem_bound, + + internals: &mut local_backing.internals.0, }, local_functions: local_backing.local_functions.as_ptr(), @@ -249,6 +294,8 @@ impl Ctx { memory_base: mem_base, memory_bound: mem_bound, + + internals: &mut local_backing.internals.0, }, local_functions: local_backing.local_functions.as_ptr(), @@ -303,6 +350,18 @@ impl Ctx { pub fn dynamic_sigindice_count(&self) -> usize { unsafe { (*self.local_backing).dynamic_sigindices.len() } } + + /// Returns the value of the specified internal field. + pub fn get_internal(&self, field: &InternalField) -> u64 { + unsafe { (*self.internal.internals)[field.index()] } + } + + /// Writes the value to the specified internal field. + pub fn set_internal(&mut self, field: &InternalField, value: u64) { + unsafe { + (*self.internal.internals)[field.index()] = value; + } + } } #[doc(hidden)] @@ -356,9 +415,13 @@ impl Ctx { 11 * (mem::size_of::() as u8) } - pub fn offset_local_functions() -> u8 { + pub fn offset_internals() -> u8 { 12 * (mem::size_of::() as u8) } + + pub fn offset_local_functions() -> u8 { + 13 * (mem::size_of::() as u8) + } } enum InnerFunc {} @@ -572,6 +635,11 @@ mod vm_offset_tests { offset_of!(InternalCtx => memory_bound).get_byte_offset(), ); + assert_eq!( + Ctx::offset_internals() as usize, + offset_of!(InternalCtx => internals).get_byte_offset(), + ); + assert_eq!( Ctx::offset_local_functions() as usize, offset_of!(Ctx => local_functions).get_byte_offset(), @@ -684,6 +752,8 @@ mod vm_ctx_tests { dynamic_sigindices: Map::new().into_boxed_map(), local_functions: Map::new().into_boxed_map(), + + internals: crate::backing::Internals([0; crate::backing::INTERNALS_SIZE]), }; let mut import_backing = ImportBacking { memories: Map::new().into_boxed_map(), diff --git a/lib/singlepass-backend/src/codegen_x64.rs b/lib/singlepass-backend/src/codegen_x64.rs index b2825f77d16..9baa15c22d8 100644 --- a/lib/singlepass-backend/src/codegen_x64.rs +++ b/lib/singlepass-backend/src/codegen_x64.rs @@ -27,7 +27,7 @@ use wasmer_runtime_core::{ FuncIndex, FuncSig, GlobalIndex, LocalFuncIndex, LocalOrImport, MemoryIndex, SigIndex, TableIndex, Type, }, - vm::{self, LocalGlobal, LocalTable}, + vm::{self, LocalGlobal, LocalTable, INTERNALS_SIZE}, }; use wasmparser::{Operator, Type as WpType}; @@ -1504,6 +1504,7 @@ impl FunctionCodeGenerator for X64FunctionCode { let op = match ev { Event::Wasm(x) => x, + Event::WasmOwned(ref x) => x, Event::Internal(x) => { match x { InternalEvent::Breakpoint(callback) => { @@ -1513,8 +1514,71 @@ impl FunctionCodeGenerator for X64FunctionCode { .unwrap() .insert(a.get_offset(), callback); } - InternalEvent::FunctionBegin(_) | InternalEvent::FunctionEnd => {} - _ => unimplemented!(), + InternalEvent::FunctionBegin(_) | InternalEvent::FunctionEnd => {}, + InternalEvent::GetInternal(idx) => { + let idx = idx as usize; + assert!(idx < INTERNALS_SIZE); + + let tmp = self.machine.acquire_temp_gpr().unwrap(); + + // Load `internals` pointer. + a.emit_mov( + Size::S64, + Location::Memory( + Machine::get_vmctx_reg(), + vm::Ctx::offset_internals() as i32, + ), + Location::GPR(tmp), + ); + + let loc = self.machine.acquire_locations( + a, + &[WpType::I64], + false, + )[0]; + self.value_stack.push((loc, LocalOrTemp::Temp)); + + // Move internal into the result location. + Self::emit_relaxed_binop( + a, + &mut self.machine, + Assembler::emit_mov, + Size::S64, + Location::Memory(tmp, (idx * 8) as i32), + loc, + ); + + self.machine.release_temp_gpr(tmp); + } + InternalEvent::SetInternal(idx) => { + let idx = idx as usize; + assert!(idx < INTERNALS_SIZE); + + let tmp = self.machine.acquire_temp_gpr().unwrap(); + + // Load `internals` pointer. + a.emit_mov( + Size::S64, + Location::Memory( + Machine::get_vmctx_reg(), + vm::Ctx::offset_internals() as i32, + ), + Location::GPR(tmp), + ); + let loc = get_location_released(a, &mut self.machine, self.value_stack.pop().unwrap()); + + // Move internal into storage. + Self::emit_relaxed_binop( + a, + &mut self.machine, + Assembler::emit_mov, + Size::S64, + loc, + Location::Memory(tmp, (idx * 8) as i32), + ); + self.machine.release_temp_gpr(tmp); + } + //_ => unimplemented!(), } return Ok(()); } @@ -2644,7 +2708,14 @@ impl FunctionCodeGenerator for X64FunctionCode { let tmp_out = self.machine.acquire_temp_gpr().unwrap(); let tmp_in = self.machine.acquire_temp_xmm().unwrap(); - a.emit_mov(Size::S32, loc, Location::XMM(tmp_in)); + Self::emit_relaxed_binop( + a, + &mut self.machine, + Assembler::emit_mov, + Size::S32, + loc, + Location::XMM(tmp_in), + ); Self::emit_f32_int_conv_check(a, &mut self.machine, tmp_in, -1.0, 4294967296.0); a.emit_cvttss2si_64(XMMOrMemory::XMM(tmp_in), tmp_out); @@ -2662,7 +2733,14 @@ impl FunctionCodeGenerator for X64FunctionCode { let tmp_out = self.machine.acquire_temp_gpr().unwrap(); let tmp_in = self.machine.acquire_temp_xmm().unwrap(); - a.emit_mov(Size::S32, loc, Location::XMM(tmp_in)); + Self::emit_relaxed_binop( + a, + &mut self.machine, + Assembler::emit_mov, + Size::S32, + loc, + Location::XMM(tmp_in), + ); Self::emit_f32_int_conv_check( a, &mut self.machine, @@ -2686,7 +2764,14 @@ impl FunctionCodeGenerator for X64FunctionCode { let tmp_out = self.machine.acquire_temp_gpr().unwrap(); let tmp_in = self.machine.acquire_temp_xmm().unwrap(); - a.emit_mov(Size::S32, loc, Location::XMM(tmp_in)); + Self::emit_relaxed_binop( + a, + &mut self.machine, + Assembler::emit_mov, + Size::S32, + loc, + Location::XMM(tmp_in), + ); Self::emit_f32_int_conv_check( a, &mut self.machine, @@ -2724,7 +2809,14 @@ impl FunctionCodeGenerator for X64FunctionCode { let tmp_out = self.machine.acquire_temp_gpr().unwrap(); let tmp_in = self.machine.acquire_temp_xmm().unwrap(); // xmm2 - a.emit_mov(Size::S32, loc, Location::XMM(tmp_in)); + Self::emit_relaxed_binop( + a, + &mut self.machine, + Assembler::emit_mov, + Size::S32, + loc, + Location::XMM(tmp_in), + ); Self::emit_f32_int_conv_check( a, &mut self.machine, @@ -2772,7 +2864,14 @@ impl FunctionCodeGenerator for X64FunctionCode { let tmp_out = self.machine.acquire_temp_gpr().unwrap(); let tmp_in = self.machine.acquire_temp_xmm().unwrap(); - a.emit_mov(Size::S64, loc, Location::XMM(tmp_in)); + Self::emit_relaxed_binop( + a, + &mut self.machine, + Assembler::emit_mov, + Size::S64, + loc, + Location::XMM(tmp_in), + ); Self::emit_f64_int_conv_check(a, &mut self.machine, tmp_in, -1.0, 4294967296.0); a.emit_cvttsd2si_64(XMMOrMemory::XMM(tmp_in), tmp_out); @@ -2826,7 +2925,14 @@ impl FunctionCodeGenerator for X64FunctionCode { let tmp_out = self.machine.acquire_temp_gpr().unwrap(); let tmp_in = self.machine.acquire_temp_xmm().unwrap(); - a.emit_mov(Size::S64, loc, Location::XMM(tmp_in)); + Self::emit_relaxed_binop( + a, + &mut self.machine, + Assembler::emit_mov, + Size::S64, + loc, + Location::XMM(tmp_in), + ); Self::emit_f64_int_conv_check( a, &mut self.machine, @@ -2850,7 +2956,14 @@ impl FunctionCodeGenerator for X64FunctionCode { let tmp_out = self.machine.acquire_temp_gpr().unwrap(); let tmp_in = self.machine.acquire_temp_xmm().unwrap(); // xmm2 - a.emit_mov(Size::S64, loc, Location::XMM(tmp_in)); + Self::emit_relaxed_binop( + a, + &mut self.machine, + Assembler::emit_mov, + Size::S64, + loc, + Location::XMM(tmp_in), + ); Self::emit_f64_int_conv_check( a, &mut self.machine, diff --git a/lib/singlepass-backend/src/protect_unix.rs b/lib/singlepass-backend/src/protect_unix.rs index 6204134a721..f777c2482c6 100644 --- a/lib/singlepass-backend/src/protect_unix.rs +++ b/lib/singlepass-backend/src/protect_unix.rs @@ -35,7 +35,7 @@ extern "C" fn signal_trap_handler( let bkpt_map = BKPT_MAP.with(|x| x.borrow().last().map(|x| x.clone())); if let Some(bkpt_map) = bkpt_map { if let Some(ref x) = bkpt_map.get(&(ip as usize)) { - (x)(BkptInfo {}); + (x)(BkptInfo { throw: throw }); return; } } @@ -128,6 +128,15 @@ pub fn call_protected(f: impl FnOnce() -> T) -> Result { } } +pub unsafe fn throw(payload: Box) -> ! { + let jmp_buf = SETJMP_BUFFER.with(|buf| buf.get()); + if *jmp_buf == [0; SETJMP_BUFFER_LEN] { + ::std::process::abort(); + } + TRAP_EARLY_DATA.with(|cell| cell.replace(Some(payload))); + longjmp(jmp_buf as *mut ::nix::libc::c_void, 0xffff); +} + /// Unwinds to last protected_call. pub unsafe fn do_unwind(signum: i32, siginfo: *const c_void, ucontext: *const c_void) -> ! { // Since do_unwind is only expected to get called from WebAssembly code which doesn't hold any host resources (locks etc.) diff --git a/src/bin/wasmer.rs b/src/bin/wasmer.rs index 021a7eaf0d8..b53fb3e0336 100644 --- a/src/bin/wasmer.rs +++ b/src/bin/wasmer.rs @@ -16,19 +16,21 @@ use structopt::StructOpt; use wasmer::*; use wasmer_clif_backend::CraneliftCompiler; #[cfg(feature = "backend:llvm")] -use wasmer_llvm_backend::LLVMCompiler; +use wasmer_llvm_backend::code::LLVMModuleCodeGenerator; use wasmer_runtime::{ cache::{Cache as BaseCache, FileSystemCache, WasmHash, WASMER_VERSION_HASH}, error::RuntimeError, Func, Value, }; +#[cfg(feature = "backend:singlepass")] +use wasmer_runtime_core::codegen::{MiddlewareChain, StreamingCompiler}; use wasmer_runtime_core::{ self, backend::{Compiler, CompilerConfig, MemoryBoundCheckMode}, loader::{Instance as LoadedInstance, LocalLoader}, }; #[cfg(feature = "backend:singlepass")] -use wasmer_singlepass_backend::SinglePassCompiler; +use wasmer_singlepass_backend::ModuleCodeGenerator as SinglePassMCG; #[cfg(feature = "wasi")] use wasmer_wasi; @@ -118,6 +120,9 @@ struct Run { /// Application arguments #[structopt(name = "--", raw(multiple = "true"))] args: Vec, + + #[structopt(long = "inst-limit")] + instruction_limit: Option, } #[allow(dead_code)] @@ -339,12 +344,33 @@ fn execute_wasm(options: &Run) -> Result<(), String> { let compiler: Box = match options.backend { #[cfg(feature = "backend:singlepass")] - Backend::Singlepass => Box::new(SinglePassCompiler::new()), + Backend::Singlepass => { + let c: StreamingCompiler = StreamingCompiler::new(|| { + let mut chain = MiddlewareChain::new(); + use wasmer_middleware_common::metering::Metering; + if let Some(limit) = options.instruction_limit { + chain.push(Metering::new(limit)); + } + chain + }); + Box::new(c) + } #[cfg(not(feature = "backend:singlepass"))] Backend::Singlepass => return Err("The singlepass backend is not enabled".to_string()), Backend::Cranelift => Box::new(CraneliftCompiler::new()), #[cfg(feature = "backend:llvm")] - Backend::LLVM => Box::new(LLVMCompiler::new()), + Backend::LLVM => { + let c: StreamingCompiler = + StreamingCompiler::new(|| { + let mut chain = MiddlewareChain::new(); + use wasmer_middleware_common::metering::Metering; + if let Some(limit) = options.instruction_limit { + chain.push(Metering::new(limit)); + } + chain + }); + Box::new(c) + } #[cfg(not(feature = "backend:llvm"))] Backend::LLVM => return Err("the llvm backend is not enabled".to_string()), };