forked from wasmerio/wasmer
-
Notifications
You must be signed in to change notification settings - Fork 0
/
engine_headless.rs
153 lines (136 loc) · 5.64 KB
/
engine_headless.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
//! Defining an engine in Wasmer is one of the fundamental steps.
//!
//! This example illustrates a neat feature of engines: their ability
//! to run in a headless mode. At the time of writing, all engines
//! have a headless mode, but it's not a requirement of the `Engine`
//! trait (defined in the `wasmer_engine` crate).
//!
//! What problem does it solve, and what does it mean?
//!
//! Once a Wasm module is compiled into executable code and stored
//! somewhere (e.g. in memory with the JIT engine, or in a native
//! object with the native engine), the module can be instantiated and
//! executed. But imagine for a second the following scenario:
//!
//! * Modules are compiled ahead of time, to be instantiated later
//! on.
//! * Modules are cross-compiled on a machine ahead of time
//! to be run on another machine later one.
//!
//! In both scenarios, the environment where the compiled Wasm module
//! will be executed can be very constrained. For such particular
//! contexts, Wasmer can be compiled _without_ the compilers, so that
//! the `wasmer` binary is as small as possible. Indeed, there is no
//! need for a compiler since the Wasm module is already compiled. All
//! we need is an engine that _only_ drives the instantiation and
//! execution of the Wasm module.
//!
//! And that, that's a headless engine.
//!
//! To achieve such a scenario, a Wasm module must be compiled, then
//! serialized —for example into a file—, then later, potentially on
//! another machine, deserialized. The next steps are classical: The
//! Wasm module is instantiated and executed.
//!
//! This example uses a `compiler` because it illustrates the entire
//! workflow, but keep in mind the compiler isn't required after the
//! compilation step.
//!
//! You can run the example directly by executing in Wasmer root:
//!
//! ```shell
//! cargo run --example engine-headless --release --features "cranelift"
//! ```
//!
//! Ready?
use tempfile::NamedTempFile;
use wasmer::imports;
use wasmer::wat2wasm;
use wasmer::Instance;
use wasmer::Module;
use wasmer::Store;
use wasmer::Value;
use wasmer_compiler_cranelift::Cranelift;
use wasmer_engine_native::Native;
fn main() -> Result<(), Box<dyn std::error::Error>> {
// First step, let's compile the Wasm module and serialize it.
// Note: we need a compiler here.
let serialized_module_file = {
// Let's declare the Wasm module with the text representation.
let wasm_bytes = wat2wasm(
r#"
(module
(type $sum_t (func (param i32 i32) (result i32)))
(func $sum_f (type $sum_t) (param $x i32) (param $y i32) (result i32)
local.get $x
local.get $y
i32.add)
(export "sum" (func $sum_f)))
"#
.as_bytes(),
)?;
// Define a compiler configuration.
//
// In this situation, the compiler is
// `wasmer_compiler_cranelift`. The compiler is responsible to
// compile the Wasm module into executable code.
let mut compiler_config = Cranelift::default();
println!("Creating Native engine...");
// Define the engine that will drive everything.
//
// In this case, the engine is `wasmer_engine_native` which
// means that a native object is going to be generated. So
// when we are going to serialize the compiled Wasm module, we
// are going to store it in a file with the `.so` extension
// for example (or `.dylib`, or `.dll` depending of the
// platform).
let engine = Native::new(&mut compiler_config).engine();
// Create a store, that holds the engine.
let store = Store::new(&engine);
println!("Compiling module...");
// Let's compile the Wasm module.
let module = Module::new(&store, wasm_bytes)?;
println!("Serializing module...");
// Here we go. Let's serialize the compiled Wasm module in a
// file.
let serialized_module_file = NamedTempFile::new()?;
module.serialize_to_file(&serialized_module_file)?;
serialized_module_file
};
// Second step, deserialize the compiled Wasm module, and execute
// it, for example with Wasmer without a compiler.
{
println!("Creating headless Native engine...");
// We create a headless Native engine.
let engine = Native::headless().engine();
let store = Store::new(&engine);
println!("Deserializing module...");
// Here we go.
//
// Deserialize the compiled Wasm module. This code is unsafe
// because Wasmer can't assert the bytes are valid (see the
// `wasmer::Module::deserialize`'s documentation to learn
// more).
let module = unsafe { Module::deserialize_from_file(&store, serialized_module_file) }?;
// Congrats, the Wasm module has been deserialized! Now let's
// execute it for the sake of having a complete example.
// Create an import object. Since our Wasm module didn't declare
// any imports, it's an empty object.
let import_object = imports! {};
println!("Instantiating module...");
// Let's instantiate the Wasm module.
let instance = Instance::new(&module, &import_object)?;
println!("Calling `sum` function...");
// The Wasm module exports a function called `sum`.
let sum = instance.exports.get_function("sum")?;
let results = sum.call(&[Value::I32(1), Value::I32(2)])?;
println!("Results: {:?}", results);
assert_eq!(results.to_vec(), vec![Value::I32(3)]);
}
Ok(())
}
#[test]
#[cfg(not(any(windows, target_arch = "aarch64")))]
fn test_engine_headless() -> Result<(), Box<dyn std::error::Error>> {
main()
}