Skip to content

Commit

Permalink
feat(examples): Add metering example
Browse files Browse the repository at this point in the history
  • Loading branch information
jubianchi committed Dec 3, 2020
1 parent 35ece9c commit d5315ec
Show file tree
Hide file tree
Showing 3 changed files with 137 additions and 3 deletions.
5 changes: 5 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -255,3 +255,8 @@ required-features = ["cranelift"]
name = "hello-world"
path = "examples/hello_world.rs"
required-features = ["cranelift"]

[[example]]
name = "metering"
path = "examples/metering.rs"
required-features = ["cranelift"]
124 changes: 124 additions & 0 deletions examples/metering.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
//! Wasmer will let you easily run Wasm module in a Rust host.
//!
//! This example illustrates the basics of using Wasmer metering features:
//!
//! 1. How to enable metering in a module
//! 2. How to meter a specific function call
//! 3. How to make execution fails if cost exceeds a given limit
//!
//! You can run the example directly by executing in Wasmer root:
//!
//! ```shell
//! cargo run --example metering --release --features "cranelift"
//! ```
//!
//! Ready?
use anyhow::bail;
use std::sync::Arc;
use wasmer::wasmparser::Operator;
use wasmer::CompilerConfig;
use wasmer::{imports, wat2wasm, Instance, Module, Store};
use wasmer_compiler_cranelift::Cranelift;
use wasmer_engine_jit::JIT;
use wasmer_middlewares::Metering;

fn main() -> anyhow::Result<()> {
// Let's declare the Wasm module.
//
// We are using the text representation of the module here but you can also load `.wasm`
// files using the `include_bytes!` macro.
let wasm_bytes = wat2wasm(
br#"
(module
(type $add_t (func (param i32) (result i32)))
(func $add_one_f (type $add_t) (param $value i32) (result i32)
local.get $value
i32.const 1
i32.add)
(export "add_one" (func $add_one_f)))
"#,
)?;

// Let's define our cost function.
//
// This function will be called for each `Operator` encountered during
// the Wasm module execution. It should return the cost of the operator
// that it received as it first argument.
let cost_function = |operator: &Operator| -> u64 {
match operator {
Operator::LocalGet { .. } | Operator::I32Const { .. } => 1,
Operator::I32Add { .. } => 2,
_ => 0,
}
};

// Now let's create our metering middleware.
//
// `Metering` need to be configured with a limit (the gas limit) and
// a cost function.
//
// For each `Operator`, the metering middleware will call the cost
// function and subtract the cost from the gas.
let metering = Arc::new(Metering::new(10, cost_function));
let mut compiler_config = Cranelift::default();
compiler_config.push_middleware(metering.clone());

// Create a Store.
//
// We use our previously create compiler configuration
// with the JIT engine.
let store = Store::new(&JIT::new(&compiler_config).engine());

println!("Compiling module...");
// Let's compile the Wasm module.
let module = Module::new(&store, wasm_bytes)?;

// Create an empty import object.
let import_object = imports! {};

println!("Instantiating module...");
// Let's instantiate the Wasm module.
let instance = Instance::new(&module, &import_object)?;

// We now have an instance ready to be used.
//
// Our module exports a single `add_one` function. We want to
// measure the cost of executing this function.
let add_one = instance
.exports
.get_function("add_one")?
.native::<i32, i32>()?;

println!("Calling `add_one` function once...");
add_one.call(1)?;

// TODO: display remaining gas points
println!("{:?}", metering.get_remaining_points(&instance));

println!("Calling `add_one` function twice...");
add_one.call(1)?;

// Because calling our `add_one` function consumes 4 gas points,
// calling it a third time will fail: we already consume 8 gas
// points, there are only two remaining.
println!("Calling `add_one` function thrice...");
match add_one.call(1) {
Ok(result) => {
bail!(
"Expected failure while calling `add_one`, found: {}",
result
);
}
Err(_) => {
println!("Calling `add_one` failed: not enough gas points remaining.");

Ok(())
}
}
}

#[test]
fn test_exported_function() -> Result<(), Box<dyn std::error::Error>> {
main()
}
11 changes: 8 additions & 3 deletions lib/middlewares/src/metering.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ use wasmer::wasmparser::{
Operator, Result as WpResult, Type as WpType, TypeOrFuncType as WpTypeOrFuncType,
};
use wasmer::{
FunctionMiddleware, GlobalInit, GlobalType, LocalFunctionIndex, MiddlewareReaderState,
ModuleMiddleware, Mutability, Type,
FunctionMiddleware, GlobalInit, GlobalType, Instance, LocalFunctionIndex,
MiddlewareReaderState, ModuleMiddleware, Mutability, Type,
};
use wasmer_types::GlobalIndex;
use wasmer_vm::ModuleInfo;
Expand All @@ -28,7 +28,7 @@ pub struct Metering<F: Fn(&Operator) -> u64 + Copy + Clone + Send + Sync> {
cost_function: F,

/// The global index in the current module for remaining points.
remaining_points_index: Mutex<Option<GlobalIndex>>,
pub remaining_points_index: Mutex<Option<GlobalIndex>>,
}

/// The function-level metering middleware.
Expand All @@ -52,6 +52,11 @@ impl<F: Fn(&Operator) -> u64 + Copy + Clone + Send + Sync> Metering<F> {
remaining_points_index: Mutex::new(None),
}
}

pub fn get_remaining_points(&self, _: &Instance) -> u32 {
// TODO: Implement this missing function
unimplemented!();
}
}

impl<F: Fn(&Operator) -> u64 + Copy + Clone + Send + Sync> fmt::Debug for Metering<F> {
Expand Down

0 comments on commit d5315ec

Please sign in to comment.