-
Notifications
You must be signed in to change notification settings - Fork 825
/
metering.rs
173 lines (150 loc) · 5.54 KB
/
metering.rs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
//! 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_universal::Universal;
use wasmer_middlewares::{
metering::{get_remaining_points, set_remaining_points, MeteringPoints},
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` needs to be configured with a limit and a cost function.
//
// For each `Operator`, the metering middleware will call the cost
// function and subtract the cost from the remaining points.
let metering = Arc::new(Metering::new(10, cost_function));
let mut compiler_config = Cranelift::default();
compiler_config.push_middleware(metering);
// Create a Store.
//
// We use our previously create compiler configuration
// with the Universal engine.
let store = Store::new(&Universal::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)?;
// As you can see here, after the first call we have 6 remaining points.
//
// This is correct, here are the details of how it has been computed:
// * `local.get $value` is a `Operator::LocalGet` which costs 1 point;
// * `i32.const` is a `Operator::I32Const` which costs 1 point;
// * `i32.add` is a `Operator::I32Add` which costs 2 points.
let remaining_points_after_first_call = get_remaining_points(&instance);
assert_eq!(
remaining_points_after_first_call,
MeteringPoints::Remaining(6)
);
println!(
"Remaining points after the first call: {:?}",
remaining_points_after_first_call
);
println!("Calling `add_one` function twice...");
add_one.call(1)?;
// We spent 4 more points with the second call.
// We have 2 remaining points.
let remaining_points_after_second_call = get_remaining_points(&instance);
assert_eq!(
remaining_points_after_second_call,
MeteringPoints::Remaining(2)
);
println!(
"Remaining points after the second call: {:?}",
remaining_points_after_second_call
);
// Because calling our `add_one` function consumes 4 points,
// calling it a third time will fail: we already consume 8
// points, there are only two remaining.
println!("Calling `add_one` function a third time...");
match add_one.call(1) {
Ok(result) => {
bail!(
"Expected failure while calling `add_one`, found: {}",
result
);
}
Err(_) => {
println!("Calling `add_one` failed.");
// Because the last needed more than the remaining points, we should have an error.
let remaining_points = get_remaining_points(&instance);
match remaining_points {
MeteringPoints::Remaining(..) => {
bail!("No metering error: there are remaining points")
}
MeteringPoints::Exhausted => println!("Not enough points remaining"),
}
}
}
// Now let's see how we can set a new limit...
println!("Set new remaining points to 10");
let new_limit = 10;
set_remaining_points(&instance, new_limit);
let remaining_points = get_remaining_points(&instance);
assert_eq!(remaining_points, MeteringPoints::Remaining(new_limit));
println!("Remaining points: {:?}", remaining_points);
Ok(())
}
#[test]
fn test_metering() -> anyhow::Result<()> {
main()
}