From e7dd725c6ddb542ba074dc54b9337086bed607ff Mon Sep 17 00:00:00 2001 From: losfair Date: Mon, 22 Jun 2020 00:54:44 +0800 Subject: [PATCH 01/13] Add module info transformation method to `ModuleMiddleware`. --- lib/api/src/lib.rs | 3 +- lib/cli/src/commands/compile.rs | 2 +- lib/compiler-cranelift/src/config.rs | 6 +-- .../src/translator/func_translator.rs | 4 +- lib/compiler-llvm/src/config.rs | 6 +-- lib/compiler-llvm/src/translator/code.rs | 6 +-- lib/compiler-singlepass/src/compiler.rs | 7 +++- lib/compiler-singlepass/src/config.rs | 6 +-- lib/compiler/src/compiler.rs | 4 +- lib/compiler/src/lib.rs | 4 +- lib/compiler/src/translator/middleware.rs | 41 ++++++++++++++----- lib/compiler/src/translator/mod.rs | 4 +- tests/compilers/middlewares.rs | 22 +++++----- tests/compilers/utils.rs | 4 +- 14 files changed, 71 insertions(+), 48 deletions(-) diff --git a/lib/api/src/lib.rs b/lib/api/src/lib.rs index 1be14f3548a..264b58ceb30 100644 --- a/lib/api/src/lib.rs +++ b/lib/api/src/lib.rs @@ -69,8 +69,7 @@ pub use crate::utils::is_wasm; pub use target_lexicon::{Architecture, CallingConvention, OperatingSystem, Triple, HOST}; #[cfg(feature = "compiler")] pub use wasmer_compiler::{ - wasmparser, CompilerConfig, FunctionMiddleware, FunctionMiddlewareGenerator, - MiddlewareReaderState, + wasmparser, CompilerConfig, FunctionMiddleware, MiddlewareReaderState, ModuleMiddleware, }; pub use wasmer_compiler::{CpuFeature, Features, Target}; pub use wasmer_engine::{ diff --git a/lib/cli/src/commands/compile.rs b/lib/cli/src/commands/compile.rs index 2d4e0c4065a..7e85bce2e74 100644 --- a/lib/cli/src/commands/compile.rs +++ b/lib/cli/src/commands/compile.rs @@ -87,7 +87,7 @@ impl Compile { if ext != recommended_extension { warning!("the output file has a wrong extension. We recommend using `{}.{}` for the chosen target", &output_filename, &recommended_extension) } - }, + } None => { warning!("the output file has no extension. We recommend using `{}.{}` for the chosen target", &output_filename, &recommended_extension) } diff --git a/lib/compiler-cranelift/src/config.rs b/lib/compiler-cranelift/src/config.rs index 748851bde90..f11cb937648 100644 --- a/lib/compiler-cranelift/src/config.rs +++ b/lib/compiler-cranelift/src/config.rs @@ -3,7 +3,7 @@ use cranelift_codegen::isa::{lookup, TargetIsa}; use cranelift_codegen::settings::{self, Configurable}; use std::sync::Arc; use wasmer_compiler::{ - Architecture, Compiler, CompilerConfig, CpuFeature, FunctionMiddlewareGenerator, Target, + Architecture, Compiler, CompilerConfig, CpuFeature, ModuleMiddleware, Target, }; // Runtime Environment @@ -35,7 +35,7 @@ pub struct Cranelift { enable_pic: bool, opt_level: OptLevel, /// The middleware chain. - pub(crate) middlewares: Vec>, + pub(crate) middlewares: Vec>, } impl Cranelift { @@ -199,7 +199,7 @@ impl CompilerConfig for Cranelift { } /// Pushes a middleware onto the back of the middleware chain. - fn push_middleware(&mut self, middleware: Arc) { + fn push_middleware(&mut self, middleware: Arc) { self.middlewares.push(middleware); } } diff --git a/lib/compiler-cranelift/src/translator/func_translator.rs b/lib/compiler-cranelift/src/translator/func_translator.rs index 94dcd8d8064..7c8ec3e4e09 100644 --- a/lib/compiler-cranelift/src/translator/func_translator.rs +++ b/lib/compiler-cranelift/src/translator/func_translator.rs @@ -19,7 +19,7 @@ use cranelift_frontend::{FunctionBuilder, FunctionBuilderContext, Variable}; use tracing::info; use wasmer_compiler::wasmparser; use wasmer_compiler::{ - to_wasm_error, wasm_unsupported, GenerateMiddlewareChain, MiddlewareBinaryReader, + to_wasm_error, wasm_unsupported, MiddlewareBinaryReader, ModuleMiddlewareChain, ModuleTranslationState, WasmResult, }; use wasmer_types::LocalFunctionIndex; @@ -75,7 +75,7 @@ impl FuncTranslator { reader.set_middleware_chain( config .middlewares - .generate_middleware_chain(local_function_index), + .generate_function_middleware_chain(local_function_index), ); self.translate_from_reader(module_translation_state, reader, func, environ) } diff --git a/lib/compiler-llvm/src/config.rs b/lib/compiler-llvm/src/config.rs index 328a365f485..a7d1e44cec2 100644 --- a/lib/compiler-llvm/src/config.rs +++ b/lib/compiler-llvm/src/config.rs @@ -8,7 +8,7 @@ use itertools::Itertools; use std::fmt::Debug; use std::sync::Arc; use target_lexicon::Architecture; -use wasmer_compiler::{Compiler, CompilerConfig, FunctionMiddlewareGenerator, Target, Triple}; +use wasmer_compiler::{Compiler, CompilerConfig, ModuleMiddleware, Target, Triple}; use wasmer_types::{FunctionType, LocalFunctionIndex}; /// The InkWell ModuleInfo type @@ -45,7 +45,7 @@ pub struct LLVM { is_pic: bool, pub(crate) callbacks: Option>, /// The middleware chain. - pub(crate) middlewares: Vec>, + pub(crate) middlewares: Vec>, } impl LLVM { @@ -212,7 +212,7 @@ impl CompilerConfig for LLVM { } /// Pushes a middleware onto the back of the middleware chain. - fn push_middleware(&mut self, middleware: Arc) { + fn push_middleware(&mut self, middleware: Arc) { self.middlewares.push(middleware); } } diff --git a/lib/compiler-llvm/src/translator/code.rs b/lib/compiler-llvm/src/translator/code.rs index 52dcde51a2a..584d2e48685 100644 --- a/lib/compiler-llvm/src/translator/code.rs +++ b/lib/compiler-llvm/src/translator/code.rs @@ -26,8 +26,8 @@ use crate::config::{CompiledKind, LLVM}; use crate::object_file::{load_object_file, CompiledFunction}; use wasmer_compiler::wasmparser::{MemoryImmediate, Operator}; use wasmer_compiler::{ - to_wasm_error, wptype_to_type, CompileError, FunctionBodyData, GenerateMiddlewareChain, - MiddlewareBinaryReader, ModuleTranslationState, RelocationTarget, Symbol, SymbolRegistry, + to_wasm_error, wptype_to_type, CompileError, FunctionBodyData, MiddlewareBinaryReader, + ModuleMiddlewareChain, ModuleTranslationState, RelocationTarget, Symbol, SymbolRegistry, }; use wasmer_types::entity::PrimaryMap; use wasmer_types::{ @@ -150,7 +150,7 @@ impl FuncTranslator { reader.set_middleware_chain( config .middlewares - .generate_middleware_chain(*local_func_index), + .generate_function_middleware_chain(*local_func_index), ); let mut params = vec![]; diff --git a/lib/compiler-singlepass/src/compiler.rs b/lib/compiler-singlepass/src/compiler.rs index 448e8f19ca4..8e57e4068d5 100644 --- a/lib/compiler-singlepass/src/compiler.rs +++ b/lib/compiler-singlepass/src/compiler.rs @@ -13,7 +13,7 @@ use wasmer_compiler::wasmparser::BinaryReaderError; use wasmer_compiler::TrapInformation; use wasmer_compiler::{Compilation, CompileError, CompiledFunction, Compiler, SectionIndex}; use wasmer_compiler::{ - CompileModuleInfo, CompilerConfig, GenerateMiddlewareChain, MiddlewareBinaryReader, + CompileModuleInfo, CompilerConfig, MiddlewareBinaryReader, ModuleMiddlewareChain, ModuleTranslationState, Target, }; use wasmer_compiler::{FunctionBody, FunctionBodyData}; @@ -73,7 +73,10 @@ impl Compiler for SinglepassCompiler { .collect::)>>() .par_iter() .map(|(i, input)| { - let middleware_chain = self.config.middlewares.generate_middleware_chain(*i); + let middleware_chain = self + .config + .middlewares + .generate_function_middleware_chain(*i); let mut reader = MiddlewareBinaryReader::new_with_offset(input.data, input.module_offset); reader.set_middleware_chain(middleware_chain); diff --git a/lib/compiler-singlepass/src/config.rs b/lib/compiler-singlepass/src/config.rs index d59fd938666..b6a198a4c97 100644 --- a/lib/compiler-singlepass/src/config.rs +++ b/lib/compiler-singlepass/src/config.rs @@ -3,7 +3,7 @@ use crate::compiler::SinglepassCompiler; use std::sync::Arc; -use wasmer_compiler::{Compiler, CompilerConfig, CpuFeature, FunctionMiddlewareGenerator, Target}; +use wasmer_compiler::{Compiler, CompilerConfig, CpuFeature, ModuleMiddleware, Target}; use wasmer_types::Features; #[derive(Debug, Clone)] @@ -11,7 +11,7 @@ pub struct Singlepass { pub(crate) enable_nan_canonicalization: bool, pub(crate) enable_stack_check: bool, /// The middleware chain. - pub(crate) middlewares: Vec>, + pub(crate) middlewares: Vec>, } impl Singlepass { @@ -66,7 +66,7 @@ impl CompilerConfig for Singlepass { } /// Pushes a middleware onto the back of the middleware chain. - fn push_middleware(&mut self, middleware: Arc) { + fn push_middleware(&mut self, middleware: Arc) { self.middlewares.push(middleware); } } diff --git a/lib/compiler/src/compiler.rs b/lib/compiler/src/compiler.rs index d94df768e5f..82071e61fb8 100644 --- a/lib/compiler/src/compiler.rs +++ b/lib/compiler/src/compiler.rs @@ -7,7 +7,7 @@ use crate::lib::std::boxed::Box; use crate::lib::std::sync::Arc; use crate::module::CompileModuleInfo; use crate::target::Target; -use crate::translator::FunctionMiddlewareGenerator; +use crate::translator::ModuleMiddleware; use crate::FunctionBodyData; use crate::ModuleTranslationState; use crate::SectionIndex; @@ -45,7 +45,7 @@ pub trait CompilerConfig { } /// Pushes a middleware onto the back of the middleware chain. - fn push_middleware(&mut self, middleware: Arc); + fn push_middleware(&mut self, middleware: Arc); } /// An implementation of a Compiler from parsed WebAssembly module to Compiled native code. diff --git a/lib/compiler/src/lib.rs b/lib/compiler/src/lib.rs index 10f5c32ae51..e9171cc7b35 100644 --- a/lib/compiler/src/lib.rs +++ b/lib/compiler/src/lib.rs @@ -86,8 +86,8 @@ pub use crate::target::{ #[cfg(feature = "translator")] pub use crate::translator::{ to_wasm_error, translate_module, wptype_to_type, FunctionBodyData, FunctionMiddleware, - FunctionMiddlewareGenerator, GenerateMiddlewareChain, MiddlewareBinaryReader, - MiddlewareReaderState, ModuleEnvironment, ModuleInfoTranslation, ModuleTranslationState, + MiddlewareBinaryReader, MiddlewareReaderState, ModuleEnvironment, ModuleInfoTranslation, + ModuleMiddleware, ModuleMiddlewareChain, ModuleTranslationState, }; pub use crate::trap::TrapInformation; pub use crate::unwind::CompiledFunctionUnwindInfo; diff --git a/lib/compiler/src/translator/middleware.rs b/lib/compiler/src/translator/middleware.rs index 51dc1fcd3c9..b53972f5aa5 100644 --- a/lib/compiler/src/translator/middleware.rs +++ b/lib/compiler/src/translator/middleware.rs @@ -6,17 +6,28 @@ use std::collections::VecDeque; use std::fmt::Debug; use std::ops::Deref; use wasmer_types::LocalFunctionIndex; +use wasmer_vm::ModuleInfo; use wasmparser::{BinaryReader, Operator, Result as WpResult, Type}; /// A shared builder for function middlewares. -pub trait FunctionMiddlewareGenerator: Debug + Send + Sync { +pub trait ModuleMiddleware: Debug + Send + Sync { /// Generates a `FunctionMiddleware` for a given function. - fn generate(&self, local_function_index: LocalFunctionIndex) -> Box; + /// + /// Here we generate a separate object for each function instead of executing directly on per-function operators, + /// in order to enable concurrent middleware application. Takes immutable `&self` because this function can be called + /// concurrently from multiple compilation threads. + fn generate_function_middleware( + &self, + local_function_index: LocalFunctionIndex, + ) -> Box; + + /// Transforms a `ModuleInfo` struct in-place. This is called before application on functions begins. + fn transform_module_info(&self, _: &mut ModuleInfo) {} } /// A function middleware specialized for a single function. pub trait FunctionMiddleware: Debug { - /// Processes the given event, module info and sink. + /// Processes the given operator. fn feed<'a>( &mut self, operator: Operator<'a>, @@ -48,24 +59,34 @@ pub struct MiddlewareReaderState<'a> { } /// Trait for generating middleware chains from "prototype" (generator) chains. -pub trait GenerateMiddlewareChain { - /// Generates a middleware chain. - fn generate_middleware_chain( +pub trait ModuleMiddlewareChain { + /// Generates a function middleware chain. + fn generate_function_middleware_chain( &self, local_function_index: LocalFunctionIndex, ) -> Vec>; + + /// Applies the chain on a `ModuleInfo` struct. + fn apply_on_module_info(&self, module_info: &mut ModuleInfo); } -impl> GenerateMiddlewareChain for [T] { - /// Generates a middleware chain. - fn generate_middleware_chain( +impl> ModuleMiddlewareChain for [T] { + /// Generates a function middleware chain. + fn generate_function_middleware_chain( &self, local_function_index: LocalFunctionIndex, ) -> Vec> { self.iter() - .map(|x| x.generate(local_function_index)) + .map(|x| x.generate_function_middleware(local_function_index)) .collect() } + + /// Applies the chain on a `ModuleInfo` struct. + fn apply_on_module_info(&self, module_info: &mut ModuleInfo) { + for item in self { + item.transform_module_info(module_info); + } + } } impl<'a> MiddlewareReaderState<'a> { diff --git a/lib/compiler/src/translator/mod.rs b/lib/compiler/src/translator/mod.rs index 210e9012ea6..a3b4fcf178c 100644 --- a/lib/compiler/src/translator/mod.rs +++ b/lib/compiler/src/translator/mod.rs @@ -16,8 +16,8 @@ mod sections; pub use self::environ::{FunctionBodyData, ModuleEnvironment, ModuleInfoTranslation}; pub use self::error::to_wasm_error; pub use self::middleware::{ - FunctionMiddleware, FunctionMiddlewareGenerator, GenerateMiddlewareChain, - MiddlewareBinaryReader, MiddlewareReaderState, + FunctionMiddleware, MiddlewareBinaryReader, MiddlewareReaderState, ModuleMiddleware, + ModuleMiddlewareChain, }; pub use self::module::translate_module; pub use self::sections::wptype_to_type; diff --git a/tests/compilers/middlewares.rs b/tests/compilers/middlewares.rs index 7b350109e0a..939c72b090f 100644 --- a/tests/compilers/middlewares.rs +++ b/tests/compilers/middlewares.rs @@ -15,8 +15,8 @@ struct Add2Mul { value_off: i32, } -impl FunctionMiddlewareGenerator for Add2MulGen { - fn generate<'a>(&self, _: LocalFunctionIndex) -> Box { +impl ModuleMiddleware for Add2MulGen { + fn generate_function_middleware(&self, _: LocalFunctionIndex) -> Box { Box::new(Add2Mul { value_off: self.value_off, }) @@ -55,8 +55,8 @@ struct Fusion { state: i32, } -impl FunctionMiddlewareGenerator for FusionGen { - fn generate<'a>(&self, _: LocalFunctionIndex) -> Box { +impl ModuleMiddleware for FusionGen { + fn generate_function_middleware(&self, _: LocalFunctionIndex) -> Box { Box::new(Fusion { state: 0 }) } } @@ -91,7 +91,7 @@ impl FunctionMiddleware for Fusion { #[test] fn middleware_basic() -> Result<()> { let store = get_store_with_middlewares(std::iter::once( - Arc::new(Add2MulGen { value_off: 0 }) as Arc + Arc::new(Add2MulGen { value_off: 0 }) as Arc )); let wat = r#"(module (func (export "add") (param i32 i32) (result i32) @@ -113,7 +113,7 @@ fn middleware_basic() -> Result<()> { #[test] fn middleware_one_to_multi() -> Result<()> { let store = get_store_with_middlewares(std::iter::once( - Arc::new(Add2MulGen { value_off: 1 }) as Arc + Arc::new(Add2MulGen { value_off: 1 }) as Arc )); let wat = r#"(module (func (export "add") (param i32 i32) (result i32) @@ -135,7 +135,7 @@ fn middleware_one_to_multi() -> Result<()> { #[test] fn middleware_multi_to_one() -> Result<()> { let store = get_store_with_middlewares(std::iter::once( - Arc::new(FusionGen) as Arc + Arc::new(FusionGen) as Arc )); let wat = r#"(module (func (export "testfunc") (param i32 i32) (result i32) @@ -161,8 +161,8 @@ fn middleware_multi_to_one() -> Result<()> { fn middleware_chain_order_1() -> Result<()> { let store = get_store_with_middlewares( vec![ - Arc::new(Add2MulGen { value_off: 0 }) as Arc, - Arc::new(Add2MulGen { value_off: 2 }) as Arc, + Arc::new(Add2MulGen { value_off: 0 }) as Arc, + Arc::new(Add2MulGen { value_off: 2 }) as Arc, ] .into_iter(), ); @@ -187,8 +187,8 @@ fn middleware_chain_order_1() -> Result<()> { fn middleware_chain_order_2() -> Result<()> { let store = get_store_with_middlewares( vec![ - Arc::new(Add2MulGen { value_off: 2 }) as Arc, - Arc::new(Add2MulGen { value_off: 0 }) as Arc, + Arc::new(Add2MulGen { value_off: 2 }) as Arc, + Arc::new(Add2MulGen { value_off: 0 }) as Arc, ] .into_iter(), ); diff --git a/tests/compilers/utils.rs b/tests/compilers/utils.rs index 7173ecc20ac..9936cca0400 100644 --- a/tests/compilers/utils.rs +++ b/tests/compilers/utils.rs @@ -1,5 +1,5 @@ use std::sync::Arc; -use wasmer::{FunctionMiddlewareGenerator, Store}; +use wasmer::{ModuleMiddleware, Store}; use wasmer_compiler::CompilerConfig; use wasmer_engine::Engine; #[cfg(feature = "test-jit")] @@ -50,7 +50,7 @@ pub fn get_store(canonicalize_nans: bool) -> Store { Store::new(&get_engine(canonicalize_nans)) } -pub fn get_store_with_middlewares>>( +pub fn get_store_with_middlewares>>( middlewares: I, ) -> Store { let mut compiler_config = get_compiler(false); From 65377200c00b5a8c07c81ce01cb21f0c35133354 Mon Sep 17 00:00:00 2001 From: losfair Date: Mon, 22 Jun 2020 00:55:09 +0800 Subject: [PATCH 02/13] Add metering middleware. --- Cargo.lock | 10 +++++ Cargo.toml | 3 ++ lib/middlewares/Cargo.toml | 19 +++++++++ lib/middlewares/README.md | 5 +++ lib/middlewares/src/lib.rs | 3 ++ lib/middlewares/src/metering.rs | 76 +++++++++++++++++++++++++++++++++ 6 files changed, 116 insertions(+) create mode 100644 lib/middlewares/Cargo.toml create mode 100644 lib/middlewares/README.md create mode 100644 lib/middlewares/src/lib.rs create mode 100644 lib/middlewares/src/metering.rs diff --git a/Cargo.lock b/Cargo.lock index 1e0b100d90a..f99a827550b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2591,6 +2591,15 @@ dependencies = [ "tempfile", ] +[[package]] +name = "wasmer-middlewares" +version = "1.0.0-alpha5" +dependencies = [ + "wasmer", + "wasmer-types", + "wasmer-vm", +] + [[package]] name = "wasmer-object" version = "1.0.0-alpha5" @@ -2696,6 +2705,7 @@ dependencies = [ "wasmer-engine-jit", "wasmer-engine-native", "wasmer-engine-object-file", + "wasmer-middlewares", "wasmer-types", "wasmer-wasi", "wasmer-wast", diff --git a/Cargo.toml b/Cargo.toml index 1039de538a8..580f045cacd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,6 +23,7 @@ wasmer-wasi = { version = "1.0.0-alpha5", path = "lib/wasi", optional = true } wasmer-wast = { version = "1.0.0-alpha5", path = "tests/lib/wast", optional = true } wasmer-cache = { version = "1.0.0-alpha5", path = "lib/cache", optional = true } wasmer-types = { version = "1.0.0-alpha5", path = "lib/wasmer-types" } +wasmer-middlewares = { version = "1.0.0-alpha.5", path = "lib/middlewares", optional = true } cfg-if = "1.0" [workspace] @@ -79,6 +80,7 @@ default = [ "cache", "wasi", # "emscripten", + "middlewares", ] engine = [] jit = [ @@ -117,6 +119,7 @@ llvm = [ "wasmer-compiler-llvm", "compiler", ] +middlewares = ["wasmer-middlewares"] # Testing features test-singlepass = [ diff --git a/lib/middlewares/Cargo.toml b/lib/middlewares/Cargo.toml new file mode 100644 index 00000000000..8ac948381d5 --- /dev/null +++ b/lib/middlewares/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "wasmer-middlewares" +version = "1.0.0-alpha5" +authors = ["Wasmer Engineering Team "] +description = "A collection of various useful middlewares" +license = "(Apache-2.0 WITH LLVM-exception) or MIT" +categories = ["wasm"] +keywords = ["webassembly", "wasm"] +repository = "https://github.com/wasmerio/wasmer" +readme = "README.md" +edition = "2018" + +[dependencies] +wasmer = { path = "../api", version = "1.0.0-alpha5" } +wasmer-types = { path = "../wasmer-types", version = "1.0.0-alpha5" } +wasmer-vm = { path = "../vm", version = "1.0.0-alpha5" } + +[badges] +maintenance = { status = "actively-developed" } diff --git a/lib/middlewares/README.md b/lib/middlewares/README.md new file mode 100644 index 00000000000..9f40322ea2a --- /dev/null +++ b/lib/middlewares/README.md @@ -0,0 +1,5 @@ +# Wasmer Middlewares + +The `wasmer-middlewares` crate is a collection of various useful middlewares: + +- `metering`: A middleware for tracking how many operators are executed in total and putting a limit on the total number of operators executed. diff --git a/lib/middlewares/src/lib.rs b/lib/middlewares/src/lib.rs new file mode 100644 index 00000000000..946c6b3c030 --- /dev/null +++ b/lib/middlewares/src/lib.rs @@ -0,0 +1,3 @@ +mod metering; + +pub use metering::Metering; diff --git a/lib/middlewares/src/metering.rs b/lib/middlewares/src/metering.rs new file mode 100644 index 00000000000..db7364b50ff --- /dev/null +++ b/lib/middlewares/src/metering.rs @@ -0,0 +1,76 @@ +//! `metering` is a middleware for tracking how many operators are executed in total +//! and putting a limit on the total number of operators executed. + +use std::fmt; +use std::sync::Mutex; +use wasmer::wasmparser::{BinaryReader, Operator, Result as WpResult}; +use wasmer::{ + FunctionMiddleware, GlobalInit, GlobalType, LocalFunctionIndex, ModuleMiddleware, Mutability, + Type, +}; +use wasmer_types::GlobalIndex; +use wasmer_vm::ModuleInfo; + +/// The module-level metering middleware. +/// +/// # Panic +/// +/// An instance of `Metering` should not be shared among different modules, since it tracks +/// module-specific information like the global index to store metering state. Attempts to use +/// a `Metering` instance from multiple modules will result in a panic. +pub struct Metering u64 + Send + Sync> { + /// Initial limit of points. + initial_limit: u64, + + /// Function that maps each operator to a cost in "points". + cost_function: F, + + /// The global index in the current module for remaining points. + remaining_points_index: Mutex>, +} + +impl u64 + Send + Sync> Metering { + /// Creates a `Metering` middleware. + pub fn new(initial_limit: u64, cost_function: F) -> Self { + Self { + initial_limit, + cost_function, + remaining_points_index: Mutex::new(None), + } + } +} + +impl u64 + Send + Sync> fmt::Debug for Metering { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("Metering") + .field("initial_limit", &self.initial_limit) + .field("cost_function", &"") + .field("remaining_points_index", &self.remaining_points_index) + .finish() + } +} + +impl u64 + Send + Sync> ModuleMiddleware for Metering { + /// Generates a `FunctionMiddleware` for a given function. + fn generate_function_middleware(&self, _: LocalFunctionIndex) -> Box { + unimplemented!(); + } + + /// Transforms a `ModuleInfo` struct in-place. This is called before application on functions begins. + fn transform_module_info(&self, module_info: &mut ModuleInfo) { + let mut remaining_points_index = self.remaining_points_index.lock().unwrap(); + if remaining_points_index.is_some() { + panic!("Metering::transform_module_info: Attempting to use a `Metering` middleware from multiple modules."); + } + + // Append a global for remaining points and initialize it. + *remaining_points_index = Some( + module_info + .globals + .push(GlobalType::new(Type::I64, Mutability::Var)), + ); + module_info + .global_initializers + .push(GlobalInit::I64Const(self.initial_limit as i64)); + } +} From 13a979a53da40b217b1b2bb960259fb3e89d45b5 Mon Sep 17 00:00:00 2001 From: losfair Date: Tue, 23 Jun 2020 00:56:00 +0800 Subject: [PATCH 03/13] Add metering middleware. --- lib/middlewares/src/lib.rs | 2 +- lib/middlewares/src/metering.rs | 99 ++++++++++++++++++++++++++++++--- tests/compilers/main.rs | 1 + tests/compilers/metering.rs | 34 +++++++++++ 4 files changed, 127 insertions(+), 9 deletions(-) create mode 100644 tests/compilers/metering.rs diff --git a/lib/middlewares/src/lib.rs b/lib/middlewares/src/lib.rs index 946c6b3c030..884fcf82242 100644 --- a/lib/middlewares/src/lib.rs +++ b/lib/middlewares/src/lib.rs @@ -1,3 +1,3 @@ -mod metering; +pub mod metering; pub use metering::Metering; diff --git a/lib/middlewares/src/metering.rs b/lib/middlewares/src/metering.rs index db7364b50ff..4538ce84d00 100644 --- a/lib/middlewares/src/metering.rs +++ b/lib/middlewares/src/metering.rs @@ -3,11 +3,14 @@ use std::fmt; use std::sync::Mutex; -use wasmer::wasmparser::{BinaryReader, Operator, Result as WpResult}; +use wasmer::wasmparser::{ + Operator, Result as WpResult, Type as WpType, TypeOrFuncType as WpTypeOrFuncType, +}; use wasmer::{ - FunctionMiddleware, GlobalInit, GlobalType, LocalFunctionIndex, ModuleMiddleware, Mutability, - Type, + FunctionMiddleware, GlobalInit, GlobalType, LocalFunctionIndex, MiddlewareReaderState, + ModuleMiddleware, Mutability, Type, }; +use wasmer_types::entity::EntityRef; use wasmer_types::GlobalIndex; use wasmer_vm::ModuleInfo; @@ -18,7 +21,7 @@ use wasmer_vm::ModuleInfo; /// An instance of `Metering` should not be shared among different modules, since it tracks /// module-specific information like the global index to store metering state. Attempts to use /// a `Metering` instance from multiple modules will result in a panic. -pub struct Metering u64 + Send + Sync> { +pub struct Metering u64 + Copy + Clone + Send + Sync> { /// Initial limit of points. initial_limit: u64, @@ -29,7 +32,19 @@ pub struct Metering u64 + Send + Sync> { remaining_points_index: Mutex>, } -impl u64 + Send + Sync> Metering { +/// The function-level metering middleware. +pub struct FunctionMetering u64 + Copy + Clone + Send + Sync> { + /// Function that maps each operator to a cost in "points". + cost_function: F, + + /// The global index in the current module for remaining points. + remaining_points_index: GlobalIndex, + + /// Accumulated cost of the current basic block. + accumulated_cost: u64, +} + +impl u64 + Copy + Clone + Send + Sync> Metering { /// Creates a `Metering` middleware. pub fn new(initial_limit: u64, cost_function: F) -> Self { Self { @@ -40,7 +55,7 @@ impl u64 + Send + Sync> Metering { } } -impl u64 + Send + Sync> fmt::Debug for Metering { +impl u64 + Copy + Clone + Send + Sync> fmt::Debug for Metering { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("Metering") .field("initial_limit", &self.initial_limit) @@ -50,10 +65,18 @@ impl u64 + Send + Sync> fmt::Debug for Metering { } } -impl u64 + Send + Sync> ModuleMiddleware for Metering { +impl u64 + Copy + Clone + Send + Sync + 'static> ModuleMiddleware + for Metering +{ /// Generates a `FunctionMiddleware` for a given function. fn generate_function_middleware(&self, _: LocalFunctionIndex) -> Box { - unimplemented!(); + Box::new(FunctionMetering { + cost_function: self.cost_function, + remaining_points_index: self.remaining_points_index.lock().unwrap().expect( + "Metering::generate_function_middleware: Remaining points index not set up.", + ), + accumulated_cost: 0, + }) } /// Transforms a `ModuleInfo` struct in-place. This is called before application on functions begins. @@ -74,3 +97,63 @@ impl u64 + Send + Sync> ModuleMiddleware for Metering { .push(GlobalInit::I64Const(self.initial_limit as i64)); } } + +impl u64 + Copy + Clone + Send + Sync> fmt::Debug for FunctionMetering { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("FunctionMetering") + .field("cost_function", &"") + .field("remaining_points_index", &self.remaining_points_index) + .finish() + } +} + +impl u64 + Copy + Clone + Send + Sync> FunctionMiddleware + for FunctionMetering +{ + fn feed<'a>( + &mut self, + operator: Operator<'a>, + state: &mut MiddlewareReaderState<'a>, + ) -> WpResult<()> { + // Get the cost of the current operator, and add it to the accumulator. + // This needs to be done before the metering logic, to prevent operators like `Call` from escaping metering in some + // corner cases. + self.accumulated_cost += (self.cost_function)(&operator); + + // Possible sources and targets of a branch. Finalize the cost of the previous basic block and perform necessary checks. + match operator { + Operator::Loop { .. } // loop headers are branch targets + | Operator::End // block ends are branch targets + | Operator::Else // "else" is the "end" of an if branch + | Operator::Br { .. } // branch source + | Operator::BrTable { .. } // branch source + | Operator::BrIf { .. } // branch source + | Operator::Call { .. } // function call - branch source + | Operator::CallIndirect { .. } // function call - branch source + | Operator::Return // end of function - branch source + => { + if self.accumulated_cost > 0 { + // if unsigned(globals[remaining_points_index]) < unsigned(self.accumulated_cost) { throw(); } + state.push_operator(Operator::GlobalGet { global_index: self.remaining_points_index.as_u32() }); + state.push_operator(Operator::I64Const { value: self.accumulated_cost as i64 }); + state.push_operator(Operator::I64LtU); + state.push_operator(Operator::If { ty: WpTypeOrFuncType::Type(WpType::EmptyBlockType) }); + state.push_operator(Operator::Unreachable); // FIXME: Signal the error properly. + state.push_operator(Operator::End); + + // globals[remaining_points_index] -= self.accumulated_cost; + state.push_operator(Operator::GlobalGet { global_index: self.remaining_points_index.as_u32() }); + state.push_operator(Operator::I64Const { value: self.accumulated_cost as i64 }); + state.push_operator(Operator::I64Sub); + state.push_operator(Operator::GlobalSet { global_index: self.remaining_points_index.as_u32() }); + + self.accumulated_cost = 0; + } + } + _ => {} + } + state.push_operator(operator); + + Ok(()) + } +} diff --git a/tests/compilers/main.rs b/tests/compilers/main.rs index 135af8e2a23..1fb24c18640 100644 --- a/tests/compilers/main.rs +++ b/tests/compilers/main.rs @@ -5,6 +5,7 @@ //! on what's available on the target. mod imports; +mod metering; mod middlewares; mod multi_value_imports; mod native_functions; diff --git a/tests/compilers/metering.rs b/tests/compilers/metering.rs new file mode 100644 index 00000000000..f375cf8fc82 --- /dev/null +++ b/tests/compilers/metering.rs @@ -0,0 +1,34 @@ +use crate::utils::get_store_with_middlewares; +use anyhow::Result; +use wasmer_middlewares::Metering; + +use std::sync::Arc; +use wasmer::wasmparser::{Operator, Result as WpResult}; +use wasmer::*; + +fn cost_always_one(_: &Operator) -> u64 { + 1 +} + +#[test] +fn metering_middleware() -> Result<()> { + let store = get_store_with_middlewares(std::iter::once(Arc::new(Metering::new( + 4, + cost_always_one, + )) as Arc)); + let wat = r#"(module + (func (export "add") (param i32 i32) (result i32) + (i32.add (local.get 0) + (local.get 1))) +)"#; + let module = Module::new(&store, wat).unwrap(); + + let import_object = imports! {}; + + let instance = Instance::new(&module, &import_object)?; + + let f: NativeFunc<(i32, i32), i32> = instance.exports.get_native_function("add")?; + let result = f.call(4, 6)?; + assert_eq!(result, 10); + Ok(()) +} From 9d8f315d4c655669d792d4a8942102043f9a257a Mon Sep 17 00:00:00 2001 From: losfair Date: Tue, 23 Jun 2020 23:36:16 +0800 Subject: [PATCH 04/13] Update module info in-place. --- lib/compiler-cranelift/src/compiler.rs | 13 +++++++++---- lib/compiler-llvm/src/compiler.rs | 15 ++++++++++----- lib/compiler-singlepass/src/compiler.rs | 5 ++++- lib/compiler/README.md | 2 +- lib/compiler/src/compiler.rs | 2 +- lib/engine-jit/src/artifact.rs | 4 ++-- lib/engine-native/src/artifact.rs | 9 +++++---- lib/engine-native/src/serialize.rs | 10 ++++++++++ lib/engine-object-file/src/artifact.rs | 9 +++++---- lib/engine-object-file/src/serialize.rs | 10 ++++++++++ lib/vm/src/module.rs | 4 ++-- 11 files changed, 59 insertions(+), 24 deletions(-) diff --git a/lib/compiler-cranelift/src/compiler.rs b/lib/compiler-cranelift/src/compiler.rs index 84e1ae42e1a..50445d467e9 100644 --- a/lib/compiler-cranelift/src/compiler.rs +++ b/lib/compiler-cranelift/src/compiler.rs @@ -19,11 +19,13 @@ use cranelift_codegen::{binemit, Context}; #[cfg(feature = "unwind")] use gimli::write::{Address, EhFrame, FrameTable}; use rayon::prelude::{IntoParallelRefIterator, ParallelIterator}; +use std::sync::Arc; use wasmer_compiler::CompileError; use wasmer_compiler::{CallingConvention, ModuleTranslationState, Target}; use wasmer_compiler::{ Compilation, CompileModuleInfo, CompiledFunction, CompiledFunctionFrameInfo, - CompiledFunctionUnwindInfo, Compiler, Dwarf, FunctionBody, FunctionBodyData, SectionIndex, + CompiledFunctionUnwindInfo, Compiler, Dwarf, FunctionBody, FunctionBodyData, + ModuleMiddlewareChain, SectionIndex, }; use wasmer_types::entity::{EntityRef, PrimaryMap}; use wasmer_types::{FunctionIndex, LocalFunctionIndex, SignatureIndex}; @@ -54,7 +56,7 @@ impl Compiler for CraneliftCompiler { fn compile_module( &self, target: &Target, - compile_info: &CompileModuleInfo, + compile_info: &mut CompileModuleInfo, module_translation_state: &ModuleTranslationState, function_body_inputs: PrimaryMap>, ) -> Result { @@ -62,6 +64,9 @@ impl Compiler for CraneliftCompiler { let frontend_config = isa.frontend_config(); let memory_styles = &compile_info.memory_styles; let table_styles = &compile_info.table_styles; + let mut module = (*compile_info.module).clone(); + self.config.middlewares.apply_on_module_info(&mut module); + compile_info.module = Arc::new(module); let module = &compile_info.module; let signatures = module .signatures @@ -77,7 +82,7 @@ impl Compiler for CraneliftCompiler { // FDEs will cause some issues in Linux. None } else { - use std::sync::{Arc, Mutex}; + use std::sync::Mutex; match target.triple().default_calling_convention() { Ok(CallingConvention::SystemV) => { match isa.create_systemv_cie() { @@ -125,7 +130,7 @@ impl Compiler for CraneliftCompiler { )?; let mut code_buf: Vec = Vec::new(); - let mut reloc_sink = RelocSink::new(module, func_index); + let mut reloc_sink = RelocSink::new(&module, func_index); let mut trap_sink = TrapSink::new(); let mut stackmap_sink = binemit::NullStackMapSink {}; context diff --git a/lib/compiler-llvm/src/compiler.rs b/lib/compiler-llvm/src/compiler.rs index 9b344b61ead..dca69557a9c 100644 --- a/lib/compiler-llvm/src/compiler.rs +++ b/lib/compiler-llvm/src/compiler.rs @@ -8,15 +8,16 @@ use inkwell::module::{Linkage, Module}; use inkwell::targets::FileType; use inkwell::DLLStorageClass; use rayon::prelude::{IntoParallelRefIterator, ParallelIterator}; +use std::sync::Arc; use wasmer_compiler::{ Compilation, CompileError, CompileModuleInfo, Compiler, CustomSection, CustomSectionProtection, - Dwarf, FunctionBodyData, ModuleTranslationState, RelocationTarget, SectionBody, SectionIndex, - Symbol, SymbolRegistry, Target, + Dwarf, FunctionBodyData, ModuleMiddlewareChain, ModuleTranslationState, RelocationTarget, + SectionBody, SectionIndex, Symbol, SymbolRegistry, Target, }; use wasmer_types::entity::{EntityRef, PrimaryMap}; use wasmer_types::{FunctionIndex, LocalFunctionIndex, SignatureIndex}; -//use std::sync::{Arc, Mutex}; +//use std::sync::Mutex; /// A compiler that compiles a WebAssembly module with LLVM, translating the Wasm to LLVM IR, /// optimizing it and then translating to assembly. @@ -233,13 +234,17 @@ impl Compiler for LLVMCompiler { fn compile_module<'data, 'module>( &self, target: &Target, - compile_info: &'module CompileModuleInfo, + compile_info: &'module mut CompileModuleInfo, module_translation: &ModuleTranslationState, function_body_inputs: PrimaryMap>, ) -> Result { //let data = Arc::new(Mutex::new(0)); let memory_styles = &compile_info.memory_styles; let table_styles = &compile_info.table_styles; + + let mut module = (*compile_info.module).clone(); + self.config.middlewares.apply_on_module_info(&mut module); + compile_info.module = Arc::new(module); let module = &compile_info.module; // TODO: merge constants in sections. @@ -260,7 +265,7 @@ impl Compiler for LLVMCompiler { // TODO: remove (to serialize) //let _data = data.lock().unwrap(); func_translator.translate( - &module, + module, module_translation, i, input, diff --git a/lib/compiler-singlepass/src/compiler.rs b/lib/compiler-singlepass/src/compiler.rs index 8e57e4068d5..6818d8fc251 100644 --- a/lib/compiler-singlepass/src/compiler.rs +++ b/lib/compiler-singlepass/src/compiler.rs @@ -47,7 +47,7 @@ impl Compiler for SinglepassCompiler { fn compile_module( &self, _target: &Target, - compile_info: &CompileModuleInfo, + compile_info: &mut CompileModuleInfo, _module_translation: &ModuleTranslationState, function_body_inputs: PrimaryMap>, ) -> Result { @@ -57,6 +57,9 @@ impl Compiler for SinglepassCompiler { let vmoffsets = VMOffsets::new(8, &compile_info.module); let memory_styles = &compile_info.memory_styles; let table_styles = &compile_info.table_styles; + let mut module = (*compile_info.module).clone(); + self.config.middlewares.apply_on_module_info(&mut module); + compile_info.module = Arc::new(module); let module = &compile_info.module; let import_trampolines: PrimaryMap = (0..module.num_imported_functions) .map(FunctionIndex::new) diff --git a/lib/compiler/README.md b/lib/compiler/README.md index c439cffaa17..dd6a60d3f5e 100644 --- a/lib/compiler/README.md +++ b/lib/compiler/README.md @@ -34,7 +34,7 @@ pub trait Compiler { fn compile_module<'data, 'module>( &self, target: &Target, - compile_info: &'module CompileModuleInfo, + compile_info: &'module mut CompileModuleInfo, module_translation: &ModuleTranslationState, // The list of function bodies function_body_inputs: PrimaryMap>, diff --git a/lib/compiler/src/compiler.rs b/lib/compiler/src/compiler.rs index 82071e61fb8..7f299711507 100644 --- a/lib/compiler/src/compiler.rs +++ b/lib/compiler/src/compiler.rs @@ -84,7 +84,7 @@ pub trait Compiler { fn compile_module<'data, 'module>( &self, target: &Target, - module: &'module CompileModuleInfo, + module: &'module mut CompileModuleInfo, module_translation: &ModuleTranslationState, // The list of function bodies function_body_inputs: PrimaryMap>, diff --git a/lib/engine-jit/src/artifact.rs b/lib/engine-jit/src/artifact.rs index e89af2aa2ab..e9d91e10bdc 100644 --- a/lib/engine-jit/src/artifact.rs +++ b/lib/engine-jit/src/artifact.rs @@ -70,7 +70,7 @@ impl JITArtifact { .map(|table_type| tunables.table_style(table_type)) .collect(); - let compile_info = CompileModuleInfo { + let mut compile_info = CompileModuleInfo { module: Arc::new(translation.module), features: features.clone(), memory_styles, @@ -82,7 +82,7 @@ impl JITArtifact { // Compile the Module let compilation = compiler.compile_module( &jit.target(), - &compile_info, + &mut compile_info, // SAFETY: Calling `unwrap` is correct since // `environ.translate()` above will write some data into // `module_translation_state`. diff --git a/lib/engine-native/src/artifact.rs b/lib/engine-native/src/artifact.rs index 6a3a8f1549c..c62decd64cd 100644 --- a/lib/engine-native/src/artifact.rs +++ b/lib/engine-native/src/artifact.rs @@ -176,7 +176,7 @@ impl NativeArtifact { .map(|_function_body| 0u64) .collect::>(); - let metadata = ModuleMetadata { + let mut metadata = ModuleMetadata { compile_info, prefix: engine_inner.get_prefix(&data), data_initializers, @@ -190,12 +190,13 @@ impl NativeArtifact { .expect("Should write number"); metadata_binary.extend(serialized_data); + let (compile_info, symbol_registry) = metadata.split(); let maybe_obj_bytes = compiler.experimental_native_compile_module( &target, - &metadata.compile_info, + compile_info, module_translation.as_ref().unwrap(), &function_body_inputs, - &metadata, + symbol_registry, &metadata_binary, ); @@ -216,7 +217,7 @@ impl NativeArtifact { None => { let compilation = compiler.compile_module( &target, - &metadata.compile_info, + &mut metadata.compile_info, module_translation.as_ref().unwrap(), function_body_inputs, )?; diff --git a/lib/engine-native/src/serialize.rs b/lib/engine-native/src/serialize.rs index 644b63d0383..c2a42312bcb 100644 --- a/lib/engine-native/src/serialize.rs +++ b/lib/engine-native/src/serialize.rs @@ -13,6 +13,16 @@ pub struct ModuleMetadata { pub function_body_lengths: PrimaryMap, } +impl ModuleMetadata { + pub fn split(&mut self) -> (&mut CompileModuleInfo, &dyn SymbolRegistry) { + let compile_info = &self.compile_info; + let symbol_registry = self as &dyn SymbolRegistry; + #[allow(mutable_transmutes)] + let compile_info = unsafe { std::mem::transmute::<&_, &mut _>(compile_info) }; + (compile_info, symbol_registry) + } +} + impl SymbolRegistry for ModuleMetadata { fn symbol_to_name(&self, symbol: Symbol) -> String { match symbol { diff --git a/lib/engine-object-file/src/artifact.rs b/lib/engine-object-file/src/artifact.rs index d11b9b20c0d..8efd6a3d431 100644 --- a/lib/engine-object-file/src/artifact.rs +++ b/lib/engine-object-file/src/artifact.rs @@ -162,7 +162,7 @@ impl ObjectFileArtifact { .map(|_function_body| 0u64) .collect::>(); - let metadata = ModuleMetadata { + let mut metadata = ModuleMetadata { compile_info, prefix: engine_inner.get_prefix(&data), data_initializers, @@ -194,12 +194,13 @@ impl ObjectFileArtifact { metadata_binary.extend(serialized_data); let metadata_length = metadata_binary.len(); + let (compile_info, symbol_registry) = metadata.split(); let maybe_obj_bytes = compiler.experimental_native_compile_module( &target, - &metadata.compile_info, + compile_info, module_translation.as_ref().unwrap(), &function_body_inputs, - &metadata, + symbol_registry, &metadata_binary, ); @@ -208,7 +209,7 @@ impl ObjectFileArtifact { } else { let compilation = compiler.compile_module( &target, - &metadata.compile_info, + &mut metadata.compile_info, module_translation.as_ref().unwrap(), function_body_inputs, )?; diff --git a/lib/engine-object-file/src/serialize.rs b/lib/engine-object-file/src/serialize.rs index 644b63d0383..c2a42312bcb 100644 --- a/lib/engine-object-file/src/serialize.rs +++ b/lib/engine-object-file/src/serialize.rs @@ -13,6 +13,16 @@ pub struct ModuleMetadata { pub function_body_lengths: PrimaryMap, } +impl ModuleMetadata { + pub fn split(&mut self) -> (&mut CompileModuleInfo, &dyn SymbolRegistry) { + let compile_info = &self.compile_info; + let symbol_registry = self as &dyn SymbolRegistry; + #[allow(mutable_transmutes)] + let compile_info = unsafe { std::mem::transmute::<&_, &mut _>(compile_info) }; + (compile_info, symbol_registry) + } +} + impl SymbolRegistry for ModuleMetadata { fn symbol_to_name(&self, symbol: Symbol) -> String { match symbol { diff --git a/lib/vm/src/module.rs b/lib/vm/src/module.rs index e483d583943..47f48b5d032 100644 --- a/lib/vm/src/module.rs +++ b/lib/vm/src/module.rs @@ -19,7 +19,7 @@ use wasmer_types::{ TableIndex, TableInitializer, TableType, }; -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct ModuleId { id: usize, } @@ -41,7 +41,7 @@ impl Default for ModuleId { /// A translated WebAssembly module, excluding the function bodies and /// memory initializers. -#[derive(Debug, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize, Deserialize)] pub struct ModuleInfo { /// A unique identifier (within this process) for this module. /// From 17e7feabe83284f92a4644f9085c16911cccf7dd Mon Sep 17 00:00:00 2001 From: losfair Date: Tue, 23 Jun 2020 23:36:21 +0800 Subject: [PATCH 05/13] Add more metering tests. --- tests/compilers/metering.rs | 63 ++++++++++++++++++++++++++++++++++--- 1 file changed, 59 insertions(+), 4 deletions(-) diff --git a/tests/compilers/metering.rs b/tests/compilers/metering.rs index f375cf8fc82..45df39659a5 100644 --- a/tests/compilers/metering.rs +++ b/tests/compilers/metering.rs @@ -10,10 +10,9 @@ fn cost_always_one(_: &Operator) -> u64 { 1 } -#[test] -fn metering_middleware() -> Result<()> { +fn run_add_with_limit(limit: u64) -> Result<()> { let store = get_store_with_middlewares(std::iter::once(Arc::new(Metering::new( - 4, + limit, cost_always_one, )) as Arc)); let wat = r#"(module @@ -29,6 +28,62 @@ fn metering_middleware() -> Result<()> { let f: NativeFunc<(i32, i32), i32> = instance.exports.get_native_function("add")?; let result = f.call(4, 6)?; - assert_eq!(result, 10); + Ok(()) +} + +fn run_loop(limit: u64, iter_count: i32) -> Result<()> { + let store = get_store_with_middlewares(std::iter::once(Arc::new(Metering::new( + limit, + cost_always_one, + )) as Arc)); + let wat = r#"(module + (func (export "test") (param i32) + (local i32) + (local.set 1 (i32.const 0)) + (loop + (local.get 1) + (i32.const 1) + (i32.add) + (local.tee 1) + (local.get 0) + (i32.ne) + (br_if 0) + ) + ) +)"#; + let module = Module::new(&store, wat).unwrap(); + + let import_object = imports! {}; + + let instance = Instance::new(&module, &import_object)?; + + let f: NativeFunc = instance.exports.get_native_function("test")?; + f.call(iter_count)?; + Ok(()) +} + +#[test] +fn metering_ok() -> Result<()> { + assert!(run_add_with_limit(4).is_ok()); + Ok(()) +} + +#[test] +fn metering_fail() -> Result<()> { + assert!(run_add_with_limit(3).is_err()); + Ok(()) +} + +#[test] +fn loop_once() -> Result<()> { + assert!(run_loop(12, 1).is_ok()); + assert!(run_loop(11, 1).is_err()); + Ok(()) +} + +#[test] +fn loop_twice() -> Result<()> { + assert!(run_loop(19, 2).is_ok()); + assert!(run_loop(18, 2).is_err()); Ok(()) } From 77b99a0b183d012924c16c583cf18f95dadd3062 Mon Sep 17 00:00:00 2001 From: losfair Date: Sat, 27 Jun 2020 00:42:05 +0800 Subject: [PATCH 06/13] Port a metering test from the old repo. --- tests/compilers/metering.rs | 79 +++++++++++++++++++++++++++++++++++++ 1 file changed, 79 insertions(+) diff --git a/tests/compilers/metering.rs b/tests/compilers/metering.rs index 45df39659a5..a4b9c764cdc 100644 --- a/tests/compilers/metering.rs +++ b/tests/compilers/metering.rs @@ -87,3 +87,82 @@ fn loop_twice() -> Result<()> { assert!(run_loop(18, 2).is_err()); Ok(()) } + +/// Ported from https://github.com/wasmerio/wasmer/blob/master/tests/middleware_common.rs +#[test] +fn complex_loop() -> Result<()> { + // 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)) + "#; + let store = get_store_with_middlewares(std::iter::once(Arc::new(Metering::new( + 100, + cost_always_one, + )) as Arc)); + let module = Module::new(&store, WAT).unwrap(); + + let import_object = imports! {}; + + let instance = Instance::new(&module, &import_object)?; + + let f: NativeFunc<(i32, i32), i32> = instance.exports.get_native_function("add_to")?; + + // FIXME: Since now a metering error is signaled with an `unreachable`, it is impossible to verify + // the error type. Fix this later. + f.call(10_000_000, 4).unwrap_err(); + Ok(()) +} From 89762a6d776a2ff9d53b6cfe49e4243590677fc0 Mon Sep 17 00:00:00 2001 From: Syrus Date: Tue, 7 Jul 2020 13:27:56 -0700 Subject: [PATCH 07/13] Short-circuit read_operator for empty chains --- lib/compiler/src/translator/middleware.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/lib/compiler/src/translator/middleware.rs b/lib/compiler/src/translator/middleware.rs index b53972f5aa5..7a812e469e6 100644 --- a/lib/compiler/src/translator/middleware.rs +++ b/lib/compiler/src/translator/middleware.rs @@ -128,6 +128,11 @@ impl<'a> MiddlewareBinaryReader<'a> { /// Reads the next available `Operator`. pub fn read_operator(&mut self) -> WpResult> { + if self.chain.is_empty() { + // We short-circuit in case no chain is used + return self.state.inner.read_operator(); + } + // Try to fill the `self.pending_operations` buffer, until it is non-empty. while self.state.pending_operations.is_empty() { let raw_op = self.state.inner.read_operator()?; From 8ca3693a56b9d82827edb408cdb85e305f53204e Mon Sep 17 00:00:00 2001 From: Nick Lewycky Date: Mon, 23 Nov 2020 13:56:17 -0800 Subject: [PATCH 08/13] Add support for middleware to experimental_native_compile_module and make it work on compiler-llvm. --- lib/compiler-llvm/src/compiler.rs | 8 ++++++-- lib/compiler/src/compiler.rs | 2 +- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/lib/compiler-llvm/src/compiler.rs b/lib/compiler-llvm/src/compiler.rs index dca69557a9c..a8c5d86ce0c 100644 --- a/lib/compiler-llvm/src/compiler.rs +++ b/lib/compiler-llvm/src/compiler.rs @@ -211,7 +211,7 @@ impl Compiler for LLVMCompiler { fn experimental_native_compile_module<'data, 'module>( &self, target: &Target, - module: &'module CompileModuleInfo, + compile_info: &'module mut CompileModuleInfo, module_translation: &ModuleTranslationState, // The list of function bodies function_body_inputs: &PrimaryMap>, @@ -219,9 +219,13 @@ impl Compiler for LLVMCompiler { // The metadata to inject into the wasmer_metadata section of the object file. wasmer_metadata: &[u8], ) -> Option, CompileError>> { + let mut module = (*compile_info.module).clone(); + self.config.middlewares.apply_on_module_info(&mut module); + compile_info.module = Arc::new(module); + Some(self.compile_native_object( target, - module, + compile_info, module_translation, function_body_inputs, symbol_registry, diff --git a/lib/compiler/src/compiler.rs b/lib/compiler/src/compiler.rs index 7f299711507..6d462851ebf 100644 --- a/lib/compiler/src/compiler.rs +++ b/lib/compiler/src/compiler.rs @@ -96,7 +96,7 @@ pub trait Compiler { fn experimental_native_compile_module<'data, 'module>( &self, _target: &Target, - _module: &'module CompileModuleInfo, + _module: &'module mut CompileModuleInfo, _module_translation: &ModuleTranslationState, // The list of function bodies _function_body_inputs: &PrimaryMap>, From 363a28cb55370350ec9e4a6a585bdac813d1e6fc Mon Sep 17 00:00:00 2001 From: Nick Lewycky Date: Mon, 23 Nov 2020 14:39:26 -0800 Subject: [PATCH 09/13] Fix middleware with singlepass. --- lib/compiler-singlepass/src/compiler.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/compiler-singlepass/src/compiler.rs b/lib/compiler-singlepass/src/compiler.rs index 6818d8fc251..2c90a6d3a04 100644 --- a/lib/compiler-singlepass/src/compiler.rs +++ b/lib/compiler-singlepass/src/compiler.rs @@ -54,12 +54,12 @@ impl Compiler for SinglepassCompiler { if compile_info.features.multi_value { return Err(CompileError::UnsupportedFeature("multivalue".to_string())); } - let vmoffsets = VMOffsets::new(8, &compile_info.module); let memory_styles = &compile_info.memory_styles; let table_styles = &compile_info.table_styles; let mut module = (*compile_info.module).clone(); self.config.middlewares.apply_on_module_info(&mut module); compile_info.module = Arc::new(module); + let vmoffsets = VMOffsets::new(8, &compile_info.module); let module = &compile_info.module; let import_trampolines: PrimaryMap = (0..module.num_imported_functions) .map(FunctionIndex::new) From 970af1a4ecd4b4664409943d5dc03f83bc06e5c2 Mon Sep 17 00:00:00 2001 From: Nick Lewycky Date: Mon, 23 Nov 2020 15:15:56 -0800 Subject: [PATCH 10/13] Remove shady "split the borrow" function that relied on UB. --- lib/engine-native/src/artifact.rs | 24 ++++++++++++++---------- lib/engine-native/src/serialize.rs | 24 ++++++++++++++++++------ lib/engine-object-file/src/artifact.rs | 13 +++++++++---- lib/engine-object-file/src/serialize.rs | 22 ++++++++++++++++------ 4 files changed, 57 insertions(+), 26 deletions(-) diff --git a/lib/engine-native/src/artifact.rs b/lib/engine-native/src/artifact.rs index c62decd64cd..1d7bd0e4fc3 100644 --- a/lib/engine-native/src/artifact.rs +++ b/lib/engine-native/src/artifact.rs @@ -190,13 +190,13 @@ impl NativeArtifact { .expect("Should write number"); metadata_binary.extend(serialized_data); - let (compile_info, symbol_registry) = metadata.split(); + let (mut compile_info, symbol_registry) = metadata.split(); let maybe_obj_bytes = compiler.experimental_native_compile_module( &target, - compile_info, + &mut compile_info, module_translation.as_ref().unwrap(), &function_body_inputs, - symbol_registry, + &symbol_registry, &metadata_binary, ); @@ -217,14 +217,14 @@ impl NativeArtifact { None => { let compilation = compiler.compile_module( &target, - &mut metadata.compile_info, + &mut compile_info, module_translation.as_ref().unwrap(), function_body_inputs, )?; let mut obj = get_object_for_target(&target_triple).map_err(to_compile_error)?; emit_data(&mut obj, WASMER_METADATA_SYMBOL, &metadata_binary) .map_err(to_compile_error)?; - emit_compilation(&mut obj, compilation, &metadata, &target_triple) + emit_compilation(&mut obj, compilation, &symbol_registry, &target_triple) .map_err(to_compile_error)?; let file = tempfile::Builder::new() .prefix("wasmer_native") @@ -349,8 +349,9 @@ impl NativeArtifact { let mut finished_functions: PrimaryMap = PrimaryMap::new(); for (function_local_index, _function_len) in metadata.function_body_lengths.iter() { - let function_name = - metadata.symbol_to_name(Symbol::LocalFunction(function_local_index)); + let function_name = metadata + .get_symbol_registry() + .symbol_to_name(Symbol::LocalFunction(function_local_index)); unsafe { // We use a fake function signature `fn()` because we just // want to get the function address. @@ -367,7 +368,9 @@ impl NativeArtifact { let mut finished_function_call_trampolines: PrimaryMap = PrimaryMap::with_capacity(metadata.compile_info.module.signatures.len()); for sig_index in metadata.compile_info.module.signatures.keys() { - let function_name = metadata.symbol_to_name(Symbol::FunctionCallTrampoline(sig_index)); + let function_name = metadata + .get_symbol_registry() + .symbol_to_name(Symbol::FunctionCallTrampoline(sig_index)); unsafe { let trampoline: LibrarySymbol = lib .get(function_name.as_bytes()) @@ -387,8 +390,9 @@ impl NativeArtifact { .keys() .take(metadata.compile_info.module.num_imported_functions) { - let function_name = - metadata.symbol_to_name(Symbol::DynamicFunctionTrampoline(func_index)); + let function_name = metadata + .get_symbol_registry() + .symbol_to_name(Symbol::DynamicFunctionTrampoline(func_index)); unsafe { let trampoline: LibrarySymbol = lib .get(function_name.as_bytes()) diff --git a/lib/engine-native/src/serialize.rs b/lib/engine-native/src/serialize.rs index c2a42312bcb..88fa9965d27 100644 --- a/lib/engine-native/src/serialize.rs +++ b/lib/engine-native/src/serialize.rs @@ -13,17 +13,29 @@ pub struct ModuleMetadata { pub function_body_lengths: PrimaryMap, } +pub struct ModuleMetadataSymbolRegistry<'a> { + pub prefix: &'a String, +} + impl ModuleMetadata { - pub fn split(&mut self) -> (&mut CompileModuleInfo, &dyn SymbolRegistry) { - let compile_info = &self.compile_info; - let symbol_registry = self as &dyn SymbolRegistry; - #[allow(mutable_transmutes)] - let compile_info = unsafe { std::mem::transmute::<&_, &mut _>(compile_info) }; + pub fn split<'a>( + &'a mut self, + ) -> (&'a mut CompileModuleInfo, ModuleMetadataSymbolRegistry<'a>) { + let compile_info = &mut self.compile_info; + let symbol_registry = ModuleMetadataSymbolRegistry { + prefix: &self.prefix, + }; (compile_info, symbol_registry) } + + pub fn get_symbol_registry<'a>(&'a self) -> ModuleMetadataSymbolRegistry<'a> { + ModuleMetadataSymbolRegistry { + prefix: &self.prefix, + } + } } -impl SymbolRegistry for ModuleMetadata { +impl<'a> SymbolRegistry for ModuleMetadataSymbolRegistry<'a> { fn symbol_to_name(&self, symbol: Symbol) -> String { match symbol { Symbol::LocalFunction(index) => { diff --git a/lib/engine-object-file/src/artifact.rs b/lib/engine-object-file/src/artifact.rs index 8efd6a3d431..5efacd35b04 100644 --- a/lib/engine-object-file/src/artifact.rs +++ b/lib/engine-object-file/src/artifact.rs @@ -2,7 +2,7 @@ //! done as separate steps. use crate::engine::{ObjectFileEngine, ObjectFileEngineInner}; -use crate::serialize::ModuleMetadata; +use crate::serialize::{ModuleMetadata, ModuleMetadataSymbolRegistry}; use std::collections::BTreeMap; use std::error::Error; use std::mem; @@ -39,6 +39,7 @@ pub struct ObjectFileArtifact { signatures: BoxedSlice, /// Length of the serialized metadata metadata_length: usize, + symbol_registry: ModuleMetadataSymbolRegistry, } fn to_compile_error(err: impl Error) -> CompileError { @@ -200,7 +201,7 @@ impl ObjectFileArtifact { compile_info, module_translation.as_ref().unwrap(), &function_body_inputs, - symbol_registry, + &symbol_registry, &metadata_binary, ); @@ -225,7 +226,7 @@ impl ObjectFileArtifact { let mut obj = get_object_for_target(&target_triple).map_err(to_compile_error)?; emit_data(&mut obj, WASMER_METADATA_SYMBOL, &metadata_binary) .map_err(to_compile_error)?; - emit_compilation(&mut obj, compilation, &metadata, &target_triple) + emit_compilation(&mut obj, compilation, &symbol_registry, &target_triple) .map_err(to_compile_error)?; obj.write().map_err(to_compile_error)? }; @@ -262,6 +263,7 @@ impl ObjectFileArtifact { .map(|sig| signature_registry.register(sig)) .collect::>(); + let symbol_registry = metadata.get_symbol_registry(); Ok(Self { metadata, module_bytes, @@ -272,6 +274,7 @@ impl ObjectFileArtifact { .into_boxed_slice(), signatures: signatures.into_boxed_slice(), metadata_length, + symbol_registry, }) } @@ -368,6 +371,7 @@ impl ObjectFileArtifact { finished_dynamic_function_trampolines.push(fp); } + let symbol_registry = metadata.get_symbol_registry(); Ok(Self { metadata, module_bytes: bytes.to_owned(), @@ -378,12 +382,13 @@ impl ObjectFileArtifact { .into_boxed_slice(), signatures: signatures.into_boxed_slice(), metadata_length: 0, + symbol_registry, }) } /// Get the `SymbolRegistry` used to generate the names used in the Artifact. pub fn symbol_registry(&self) -> &dyn SymbolRegistry { - &self.metadata + &self.symbol_registry } /// The length in bytes of the metadata in the serialized output. diff --git a/lib/engine-object-file/src/serialize.rs b/lib/engine-object-file/src/serialize.rs index c2a42312bcb..8ec7da0302d 100644 --- a/lib/engine-object-file/src/serialize.rs +++ b/lib/engine-object-file/src/serialize.rs @@ -13,17 +13,27 @@ pub struct ModuleMetadata { pub function_body_lengths: PrimaryMap, } +pub struct ModuleMetadataSymbolRegistry { + pub prefix: String, +} + impl ModuleMetadata { - pub fn split(&mut self) -> (&mut CompileModuleInfo, &dyn SymbolRegistry) { - let compile_info = &self.compile_info; - let symbol_registry = self as &dyn SymbolRegistry; - #[allow(mutable_transmutes)] - let compile_info = unsafe { std::mem::transmute::<&_, &mut _>(compile_info) }; + pub fn split(&mut self) -> (&mut CompileModuleInfo, ModuleMetadataSymbolRegistry) { + let compile_info = &mut self.compile_info; + let symbol_registry = ModuleMetadataSymbolRegistry { + prefix: self.prefix.clone(), + }; (compile_info, symbol_registry) } + + pub fn get_symbol_registry(&self) -> ModuleMetadataSymbolRegistry { + ModuleMetadataSymbolRegistry { + prefix: self.prefix.clone(), + } + } } -impl SymbolRegistry for ModuleMetadata { +impl SymbolRegistry for ModuleMetadataSymbolRegistry { fn symbol_to_name(&self, symbol: Symbol) -> String { match symbol { Symbol::LocalFunction(index) => { From 21369a26d3fa3d42ac214f383c386a12b15e21c0 Mon Sep 17 00:00:00 2001 From: Nick Lewycky Date: Mon, 23 Nov 2020 15:23:33 -0800 Subject: [PATCH 11/13] Clean up build warnings. --- lib/middlewares/src/metering.rs | 1 - tests/compilers/metering.rs | 4 ++-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/lib/middlewares/src/metering.rs b/lib/middlewares/src/metering.rs index 4538ce84d00..bf3a6c72e83 100644 --- a/lib/middlewares/src/metering.rs +++ b/lib/middlewares/src/metering.rs @@ -10,7 +10,6 @@ use wasmer::{ FunctionMiddleware, GlobalInit, GlobalType, LocalFunctionIndex, MiddlewareReaderState, ModuleMiddleware, Mutability, Type, }; -use wasmer_types::entity::EntityRef; use wasmer_types::GlobalIndex; use wasmer_vm::ModuleInfo; diff --git a/tests/compilers/metering.rs b/tests/compilers/metering.rs index a4b9c764cdc..77509136ca3 100644 --- a/tests/compilers/metering.rs +++ b/tests/compilers/metering.rs @@ -3,7 +3,7 @@ use anyhow::Result; use wasmer_middlewares::Metering; use std::sync::Arc; -use wasmer::wasmparser::{Operator, Result as WpResult}; +use wasmer::wasmparser::Operator; use wasmer::*; fn cost_always_one(_: &Operator) -> u64 { @@ -27,7 +27,7 @@ fn run_add_with_limit(limit: u64) -> Result<()> { let instance = Instance::new(&module, &import_object)?; let f: NativeFunc<(i32, i32), i32> = instance.exports.get_native_function("add")?; - let result = f.call(4, 6)?; + f.call(4, 6)?; Ok(()) } From 05d897098070a948d47707d5c119e9159e2434c4 Mon Sep 17 00:00:00 2001 From: Nick Lewycky Date: Mon, 23 Nov 2020 15:40:59 -0800 Subject: [PATCH 12/13] Fix the version number for the new middlewares crate. --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 580f045cacd..035a4b2427a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,7 +23,7 @@ wasmer-wasi = { version = "1.0.0-alpha5", path = "lib/wasi", optional = true } wasmer-wast = { version = "1.0.0-alpha5", path = "tests/lib/wast", optional = true } wasmer-cache = { version = "1.0.0-alpha5", path = "lib/cache", optional = true } wasmer-types = { version = "1.0.0-alpha5", path = "lib/wasmer-types" } -wasmer-middlewares = { version = "1.0.0-alpha.5", path = "lib/middlewares", optional = true } +wasmer-middlewares = { version = "1.0.0-alpha5", path = "lib/middlewares", optional = true } cfg-if = "1.0" [workspace] From 32b3daeaced6a6fbb1aff40586096ebb8d535ca8 Mon Sep 17 00:00:00 2001 From: Nick Lewycky Date: Mon, 23 Nov 2020 16:32:09 -0800 Subject: [PATCH 13/13] Add a variant of Extend that works on borrowed arrays. --- lib/compiler/src/translator/middleware.rs | 12 ++++++++++ lib/middlewares/src/metering.rs | 28 ++++++++++++----------- 2 files changed, 27 insertions(+), 13 deletions(-) diff --git a/lib/compiler/src/translator/middleware.rs b/lib/compiler/src/translator/middleware.rs index 7a812e469e6..e7f21c574b7 100644 --- a/lib/compiler/src/translator/middleware.rs +++ b/lib/compiler/src/translator/middleware.rs @@ -96,6 +96,18 @@ impl<'a> MiddlewareReaderState<'a> { } } +impl<'a> Extend> for MiddlewareReaderState<'a> { + fn extend>>(&mut self, iter: I) { + self.pending_operations.extend(iter); + } +} + +impl<'a: 'b, 'b> Extend<&'b Operator<'a>> for MiddlewareReaderState<'a> { + fn extend>>(&mut self, iter: I) { + self.pending_operations.extend(iter.into_iter().cloned()); + } +} + impl<'a> MiddlewareBinaryReader<'a> { /// Constructs a `MiddlewareBinaryReader` with an explicit starting offset. pub fn new_with_offset(data: &'a [u8], original_offset: usize) -> Self { diff --git a/lib/middlewares/src/metering.rs b/lib/middlewares/src/metering.rs index bf3a6c72e83..68a010a9794 100644 --- a/lib/middlewares/src/metering.rs +++ b/lib/middlewares/src/metering.rs @@ -132,19 +132,21 @@ impl u64 + Copy + Clone + Send + Sync> FunctionMiddleware | Operator::Return // end of function - branch source => { if self.accumulated_cost > 0 { - // if unsigned(globals[remaining_points_index]) < unsigned(self.accumulated_cost) { throw(); } - state.push_operator(Operator::GlobalGet { global_index: self.remaining_points_index.as_u32() }); - state.push_operator(Operator::I64Const { value: self.accumulated_cost as i64 }); - state.push_operator(Operator::I64LtU); - state.push_operator(Operator::If { ty: WpTypeOrFuncType::Type(WpType::EmptyBlockType) }); - state.push_operator(Operator::Unreachable); // FIXME: Signal the error properly. - state.push_operator(Operator::End); - - // globals[remaining_points_index] -= self.accumulated_cost; - state.push_operator(Operator::GlobalGet { global_index: self.remaining_points_index.as_u32() }); - state.push_operator(Operator::I64Const { value: self.accumulated_cost as i64 }); - state.push_operator(Operator::I64Sub); - state.push_operator(Operator::GlobalSet { global_index: self.remaining_points_index.as_u32() }); + state.extend(&[ + // if unsigned(globals[remaining_points_index]) < unsigned(self.accumulated_cost) { throw(); } + Operator::GlobalGet { global_index: self.remaining_points_index.as_u32() }, + Operator::I64Const { value: self.accumulated_cost as i64 }, + Operator::I64LtU, + Operator::If { ty: WpTypeOrFuncType::Type(WpType::EmptyBlockType) }, + Operator::Unreachable, // FIXME: Signal the error properly. + Operator::End, + + // globals[remaining_points_index] -= self.accumulated_cost; + Operator::GlobalGet { global_index: self.remaining_points_index.as_u32() }, + Operator::I64Const { value: self.accumulated_cost as i64 }, + Operator::I64Sub, + Operator::GlobalSet { global_index: self.remaining_points_index.as_u32() }, + ]); self.accumulated_cost = 0; }