Merge branch 'master' into command/pyodide

This commit is contained in:
Jesús Leganés-Combarro 'piranna 2019-06-08 19:48:25 +02:00
commit ff21d9e95f
22 changed files with 936 additions and 36 deletions

View File

@ -6,6 +6,7 @@ Blocks of changes will separated by version increments.
## **[Unreleased]**
- [#484](https://github.com/wasmerio/wasmer/pull/484) Fix bugs in emscripten socket syscalls
- [#476](https://github.com/wasmerio/wasmer/pull/476) Fix bug with wasi::environ_get, fix off by one error in wasi::environ_sizes_get
- [#470](https://github.com/wasmerio/wasmer/pull/470) Add mapdir support to Emscripten, implement getdents for Unix
- [#467](https://github.com/wasmerio/wasmer/pull/467) `wasmer_instantiate` returns better error messages in the runtime C API

5
Cargo.lock generated
View File

@ -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]]

View File

@ -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)

View File

@ -0,0 +1,55 @@
(module
(func $main (export "main")
(i32.const 0)
(i32.const 0)
(i32.add)
(i32.const 0)
(i32.const 0)
(i32.add)
(i32.const 0)
(i32.const 0)
(i32.add)
(i32.const 0)
(i32.const 0)
(i32.add)
(i32.const 0)
(i32.const 0)
(i32.add)
(i32.const 0)
(i32.const 0)
(i32.add)
(i32.const 0)
(i32.const 0)
(i32.add)
(i32.const 0)
(i32.const 0)
(i32.add)
(block
(i32.const 0)
(i32.const 0)
(i32.add)
(i32.const 0)
(i32.const 0)
(i32.add)
(br 0)
(unreachable)
)
(drop)
(drop)
(drop)
(drop)
(drop)
(drop)
(drop)
(drop)
)
)

View File

@ -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(());
}

View File

@ -535,13 +535,25 @@ pub fn ___syscall146(ctx: &mut Ctx, _which: i32, mut varargs: VarArgs) -> i32 {
}
pub fn ___syscall168(_ctx: &mut Ctx, _one: i32, _two: i32) -> i32 {
debug!("emscripten::___syscall168");
debug!("emscripten::___syscall168 - stub");
-1
}
pub fn ___syscall191(_ctx: &mut Ctx, _one: i32, _two: i32) -> i32 {
debug!("emscripten::___syscall191 - stub");
-1
pub fn ___syscall191(ctx: &mut Ctx, _which: i32, mut varargs: VarArgs) -> i32 {
let _resource: i32 = varargs.get(ctx);
debug!(
"emscripten::___syscall191 - mostly stub, resource: {}",
_resource
);
let rlim_emptr: i32 = varargs.get(ctx);
let rlim_ptr = emscripten_memory_pointer!(ctx.memory(0), rlim_emptr) as *mut u8;
let rlim = unsafe { slice::from_raw_parts_mut(rlim_ptr, 16) };
// set all to RLIM_INIFINTY
LittleEndian::write_i64(&mut rlim[..], -1);
LittleEndian::write_i64(&mut rlim[8..], -1);
0
}
pub fn ___syscall193(_ctx: &mut Ctx, _one: i32, _two: i32) -> i32 {
@ -753,19 +765,23 @@ pub fn ___syscall340(ctx: &mut Ctx, _which: c_int, mut varargs: VarArgs) -> c_in
debug!("emscripten::___syscall340 (prlimit64), {}", _which);
// NOTE: Doesn't really matter. Wasm modules cannot exceed WASM_PAGE_SIZE anyway.
let _pid: i32 = varargs.get(ctx);
let _resource: i32 = varargs.get(ctx);
let resource: i32 = varargs.get(ctx);
let _new_limit: u32 = varargs.get(ctx);
let old_limit: u32 = varargs.get(ctx);
let val = match resource {
// RLIMIT_NOFILE
7 => 1024,
_ => -1, // RLIM_INFINITY
};
if old_limit != 0 {
// just report no limits
let buf_ptr = emscripten_memory_pointer!(ctx.memory(0), old_limit) as *mut u8;
let buf = unsafe { slice::from_raw_parts_mut(buf_ptr, 16) };
LittleEndian::write_i32(&mut buf[..], -1); // RLIM_INFINITY
LittleEndian::write_i32(&mut buf[4..], -1); // RLIM_INFINITY
LittleEndian::write_i32(&mut buf[8..], -1); // RLIM_INFINITY
LittleEndian::write_i32(&mut buf[12..], -1); // RLIM_INFINITY
LittleEndian::write_i64(&mut buf[..], val);
LittleEndian::write_i64(&mut buf[8..], val);
}
0

View File

@ -610,15 +610,24 @@ pub fn ___syscall102(ctx: &mut Ctx, _which: c_int, mut varargs: VarArgs) -> c_in
// recvfrom (socket: c_int, buf: *const c_void, len: size_t, flags: c_int, addr: *const sockaddr, addrlen: socklen_t) -> ssize_t
let socket = socket_varargs.get(ctx);
let buf: u32 = socket_varargs.get(ctx);
let flags = socket_varargs.get(ctx);
let len: i32 = socket_varargs.get(ctx);
let flags: i32 = socket_varargs.get(ctx);
let address: u32 = socket_varargs.get(ctx);
let address_len: u32 = socket_varargs.get(ctx);
let buf_addr = emscripten_memory_pointer!(ctx.memory(0), buf) as _;
let address = emscripten_memory_pointer!(ctx.memory(0), address) as *mut sockaddr;
let address_len_addr =
emscripten_memory_pointer!(ctx.memory(0), address_len) as *mut socklen_t;
unsafe { recvfrom(socket, buf_addr, flags, len, address, address_len_addr) as i32 }
unsafe {
recvfrom(
socket,
buf_addr,
len as usize,
flags,
address,
address_len_addr,
) as i32
}
}
14 => {
debug!("socket: setsockopt");
@ -764,7 +773,10 @@ pub fn ___syscall142(ctx: &mut Ctx, _which: c_int, mut varargs: VarArgs) -> c_in
let exceptfds: u32 = varargs.get(ctx);
let _timeout: i32 = varargs.get(ctx);
assert!(nfds <= 64, "`nfds` must be less than or equal to 64");
if nfds > 1024 {
// EINVAL
return -22;
}
assert!(exceptfds == 0, "`exceptfds` is not supporrted");
let readfds_ptr = emscripten_memory_pointer!(ctx.memory(0), readfds) as _;

View File

@ -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;

View File

@ -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(),

View File

@ -8,4 +8,20 @@ authors = ["The Wasmer Engineering Team <engineering@wasmer.io>"]
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

View File

@ -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<LLVMModuleCodeGenerator, _, _, _, _> =
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<SinglePassMCG, _, _, _, _> = 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);

View File

@ -1,3 +1,4 @@
#![deny(unused_imports, unused_variables, unused_unsafe, unreachable_patterns)]
pub mod call_trace;
pub mod metering;

View File

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

View File

@ -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<SigIndex, vm::SigId>,
pub(crate) local_functions: BoxedMap<LocalFuncIndex, *const vm::Func>,
pub(crate) internals: Internals,
}
impl LocalBacking {
@ -66,6 +78,8 @@ impl LocalBacking {
dynamic_sigindices,
local_functions,
internals: Internals([0; INTERNALS_SIZE]),
}
}

View File

@ -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<dyn Any>) -> !,
}
pub trait ModuleCodeGenerator<FCG: FunctionCodeGenerator<E>, RM: RunnableModule, E: Debug> {
/// Creates a new module code generator.

View File

@ -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 {

View File

@ -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 {

View File

@ -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<usize>,
}
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::<usize>() as u8)
}
pub fn offset_local_functions() -> u8 {
pub fn offset_internals() -> u8 {
12 * (mem::size_of::<usize>() as u8)
}
pub fn offset_local_functions() -> u8 {
13 * (mem::size_of::<usize>() 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(),

View File

@ -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<CodegenError> 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<CodegenError> 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<CodegenError> 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<CodegenError> 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<CodegenError> 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<CodegenError> 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<CodegenError> 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<CodegenError> 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<CodegenError> 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,

View File

@ -257,6 +257,7 @@ impl Machine {
pub fn release_locations_keep_state<E: Emitter>(&self, assembler: &mut E, locs: &[Location]) {
let mut delta_stack_offset: usize = 0;
let mut stack_offset = self.stack_offset.0;
for loc in locs.iter().rev() {
match *loc {
@ -265,9 +266,10 @@ impl Machine {
unreachable!();
}
let offset = (-x) as usize;
if offset != self.stack_offset.0 {
if offset != stack_offset {
unreachable!();
}
stack_offset -= 8;
delta_stack_offset += 8;
}
_ => {}
@ -417,3 +419,19 @@ impl Machine {
}
}
}
#[cfg(test)]
mod test {
use super::*;
use dynasmrt::x64::Assembler;
#[test]
fn test_release_locations_keep_state_nopanic() {
let mut machine = Machine::new();
let mut assembler = Assembler::new().unwrap();
let locs = machine.acquire_locations(&mut assembler, &[WpType::I32; 10], false);
machine.release_locations_keep_state(&mut assembler, &locs);
}
}

View File

@ -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<T>(f: impl FnOnce() -> T) -> Result<T, CallProtError> {
}
}
pub unsafe fn throw(payload: Box<dyn Any>) -> ! {
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.)

View File

@ -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<String>,
#[structopt(long = "inst-limit")]
instruction_limit: Option<u64>,
}
#[allow(dead_code)]
@ -339,12 +344,33 @@ fn execute_wasm(options: &Run) -> Result<(), String> {
let compiler: Box<dyn Compiler> = match options.backend {
#[cfg(feature = "backend:singlepass")]
Backend::Singlepass => Box::new(SinglePassCompiler::new()),
Backend::Singlepass => {
let c: StreamingCompiler<SinglePassMCG, _, _, _, _> = 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<LLVMModuleCodeGenerator, _, _, _, _> =
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()),
};