1 /*
2 Example of instantiating of the WebAssembly module and invoking its exported
3 function.
4
5 You can compile and run this example on Linux with:
6
7 cargo build --release -p wasmtime-c-api
8 c++ examples/async.cc \
9 -I crates/c-api/include \
10 target/release/libwasmtime.a \
11 -std=c++11 \
12 -lpthread -ldl -lm \
13 -o async
14 ./async
15
16 Note that on Windows and macOS the command will be similar, but you'll need
17 to tweak the `-lpthread` and such annotations.
18
19 You can also build using cmake:
20
21 mkdir build && cd build && cmake .. && cmake --build . --target wasmtime-async
22 */
23
24 #include <array>
25 #include <assert.h>
26 #include <chrono>
27 #include <cstdlib>
28 #include <fstream>
29 #include <future>
30 #include <iostream>
31 #include <memory>
32 #include <optional>
33 #include <sstream>
34 #include <streambuf>
35 #include <string>
36 #include <thread>
37 #include <wasmtime.h>
38
39 namespace {
40
41 template <typename T, void (*fn)(T *)> struct deleter {
operator ()__anonaedd7ee30111::deleter42 void operator()(T *ptr) { fn(ptr); }
43 };
44 template <typename T, void (*fn)(T *)>
45 using handle = std::unique_ptr<T, deleter<T, fn>>;
46
exit_with_error(std::string msg,wasmtime_error_t * err,wasm_trap_t * trap)47 void exit_with_error(std::string msg, wasmtime_error_t *err,
48 wasm_trap_t *trap) {
49 std::cerr << "error: " << msg << std::endl;
50 wasm_byte_vec_t error_message;
51 if (err) {
52 wasmtime_error_message(err, &error_message);
53 } else {
54 wasm_trap_message(trap, &error_message);
55 }
56 std::cerr << std::string(error_message.data, error_message.size) << std::endl;
57 wasm_byte_vec_delete(&error_message);
58 std::exit(1);
59 }
60
create_engine()61 handle<wasm_engine_t, wasm_engine_delete> create_engine() {
62 wasm_config_t *config = wasm_config_new();
63 assert(config != nullptr);
64 wasmtime_config_consume_fuel_set(config, true);
65 handle<wasm_engine_t, wasm_engine_delete> engine;
66 // this takes ownership of config
67 engine.reset(wasm_engine_new_with_config(config));
68 assert(engine);
69 return engine;
70 }
71
72 handle<wasmtime_store_t, wasmtime_store_delete>
create_store(wasm_engine_t * engine)73 create_store(wasm_engine_t *engine) {
74 handle<wasmtime_store_t, wasmtime_store_delete> store;
75 store.reset(wasmtime_store_new(engine, nullptr, nullptr));
76 assert(store);
77 return store;
78 }
79
80 handle<wasmtime_linker_t, wasmtime_linker_delete>
create_linker(wasm_engine_t * engine)81 create_linker(wasm_engine_t *engine) {
82 handle<wasmtime_linker_t, wasmtime_linker_delete> linker;
83 linker.reset(wasmtime_linker_new(engine));
84 assert(linker);
85 return linker;
86 }
87
88 handle<wasmtime_module_t, wasmtime_module_delete>
compile_wat_module_from_file(wasm_engine_t * engine,const std::string & filename)89 compile_wat_module_from_file(wasm_engine_t *engine,
90 const std::string &filename) {
91 std::ifstream t(filename);
92 std::stringstream buffer;
93 buffer << t.rdbuf();
94 if (t.bad()) {
95 std::cerr << "error reading file: " << filename << std::endl;
96 std::exit(1);
97 }
98 const std::string &content = buffer.str();
99 wasm_byte_vec_t wasm_bytes;
100 handle<wasmtime_error_t, wasmtime_error_delete> error{
101 wasmtime_wat2wasm(content.data(), content.size(), &wasm_bytes)};
102 if (error) {
103 exit_with_error("failed to parse wat", error.get(), nullptr);
104 }
105 wasmtime_module_t *mod_ptr = nullptr;
106 error.reset(wasmtime_module_new(engine,
107 reinterpret_cast<uint8_t *>(wasm_bytes.data),
108 wasm_bytes.size, &mod_ptr));
109 wasm_byte_vec_delete(&wasm_bytes);
110 handle<wasmtime_module_t, wasmtime_module_delete> mod{mod_ptr};
111 if (!mod) {
112 exit_with_error("failed to compile module", error.get(), nullptr);
113 }
114 return mod;
115 }
116
117 class printer_thread_state {
118 public:
set_value_to_print(int32_t v)119 void set_value_to_print(int32_t v) {
120 _print_finished_future = _print_finished.get_future();
121 _value_to_print.set_value(v);
122 }
get_value_to_print()123 int32_t get_value_to_print() { return _value_to_print.get_future().get(); }
124
print_is_pending() const125 bool print_is_pending() const {
126 return _print_finished_future.valid() &&
127 _print_finished_future.wait_for(std::chrono::seconds(0)) !=
128 std::future_status::ready;
129 }
wait_for_print_result() const130 void wait_for_print_result() const { _print_finished_future.wait(); }
get_print_result()131 void get_print_result() { _print_finished_future.get(); }
set_print_success()132 void set_print_success() { _print_finished.set_value(); }
133
134 private:
135 std::promise<int32_t> _value_to_print;
136 std::promise<void> _print_finished;
137 std::future<void> _print_finished_future;
138 };
139
140 printer_thread_state printer_state;
141
142 struct async_call_env {
143 wasm_trap_t **trap_ret;
144 };
145
poll_print_finished_state(void * env)146 bool poll_print_finished_state(void *env) {
147 std::cout << "polling async host function result" << std::endl;
148 auto *async_env = static_cast<async_call_env *>(env);
149 // Don't block, just poll the future state.
150 if (printer_state.print_is_pending()) {
151 return false;
152 }
153 try {
154 printer_state.get_print_result();
155 } catch (const std::exception &ex) {
156 std::string msg = ex.what();
157 *async_env->trap_ret = wasmtime_trap_new(msg.data(), msg.size());
158 }
159 return true;
160 }
161 } // namespace
162
main()163 int main() {
164 // A thread that will async perform host function calls.
165 std::thread printer_thread([]() {
166 int32_t value_to_print = printer_state.get_value_to_print();
167 std::cout << "received value to print!" << std::endl;
168 std::this_thread::sleep_for(std::chrono::milliseconds(500));
169 std::cout << "printing: " << value_to_print << std::endl;
170 std::this_thread::sleep_for(std::chrono::milliseconds(500));
171 std::cout << "signaling that value is printed" << std::endl;
172 printer_state.set_print_success();
173 });
174
175 handle<wasmtime_error_t, wasmtime_error_delete> error;
176
177 auto engine = create_engine();
178 auto store = create_store(engine.get());
179 // This pointer is unowned.
180 auto *context = wasmtime_store_context(store.get());
181 // Configure the store to periodically yield control
182 wasmtime_context_set_fuel(context, 100000);
183 wasmtime_context_fuel_async_yield_interval(context, /*interval=*/10000);
184
185 auto compiled_module =
186 compile_wat_module_from_file(engine.get(), "examples/async.wat");
187
188 auto linker = create_linker(engine.get());
189 static std::string host_module_name = "host";
190 static std::string host_func_name = "print";
191
192 // Declare our async host function's signature and definition.
193 wasm_valtype_vec_t arg_types;
194 wasm_valtype_vec_t result_types;
195 wasm_valtype_vec_new_uninitialized(&arg_types, 1);
196 arg_types.data[0] = wasm_valtype_new_i32();
197 wasm_valtype_vec_new_empty(&result_types);
198 handle<wasm_functype_t, wasm_functype_delete> functype{
199 wasm_functype_new(&arg_types, &result_types)};
200
201 error.reset(wasmtime_linker_define_async_func(
202 linker.get(), host_module_name.data(), host_module_name.size(),
203 host_func_name.data(), host_func_name.size(), functype.get(),
204 [](void *, wasmtime_caller_t *, const wasmtime_val_t *args, size_t,
205 wasmtime_val_t *, size_t, wasm_trap_t **trap_ret,
206 wasmtime_async_continuation_t *continuation_ret) {
207 std::cout << "invoking async host function" << std::endl;
208 printer_state.set_value_to_print(args[0].of.i32);
209
210 continuation_ret->callback = &poll_print_finished_state;
211 continuation_ret->env = new async_call_env{trap_ret};
212 continuation_ret->finalizer = [](void *env) {
213 std::cout << "deleting async_call_env" << std::endl;
214 delete static_cast<async_call_env *>(env);
215 };
216 },
217 /*env=*/nullptr, /*finalizer=*/nullptr));
218 if (error) {
219 exit_with_error("failed to define host function", error.get(), nullptr);
220 }
221
222 // Now instantiate our module using the linker.
223 handle<wasmtime_call_future_t, wasmtime_call_future_delete> call_future;
224 wasm_trap_t *trap_ptr = nullptr;
225 wasmtime_error_t *error_ptr = nullptr;
226 wasmtime_instance_t instance;
227 call_future.reset(wasmtime_linker_instantiate_async(
228 linker.get(), context, compiled_module.get(), &instance, &trap_ptr,
229 &error_ptr));
230 while (!wasmtime_call_future_poll(call_future.get())) {
231 std::cout << "yielding instantiation!" << std::endl;
232 }
233 error.reset(error_ptr);
234 handle<wasm_trap_t, wasm_trap_delete> trap{trap_ptr};
235 if (error || trap) {
236 exit_with_error("failed to instantiate module", error.get(), trap.get());
237 }
238 // delete call future - it's no longer needed
239 call_future = nullptr;
240 // delete the linker now that we've created our instance
241 linker = nullptr;
242
243 // Grab our exported function
244 static std::string guest_func_name = "print_fibonacci";
245 wasmtime_extern_t guest_func_extern;
246 bool found =
247 wasmtime_instance_export_get(context, &instance, guest_func_name.data(),
248 guest_func_name.size(), &guest_func_extern);
249 assert(found);
250 assert(guest_func_extern.kind == WASMTIME_EXTERN_FUNC);
251
252 // Now call our print_fibonacci function with n=15
253 std::array<wasmtime_val_t, 1> args;
254 args[0].kind = WASMTIME_I32;
255 args[0].of.i32 = 15;
256 std::array<wasmtime_val_t, 0> results;
257 call_future.reset(wasmtime_func_call_async(
258 context, &guest_func_extern.of.func, args.data(), args.size(),
259 results.data(), results.size(), &trap_ptr, &error_ptr));
260 // Poll the execution of the call. This can yield control back if there is an
261 // async host call or if we ran out of fuel.
262 while (!wasmtime_call_future_poll(call_future.get())) {
263 // if we have an async host call pending then wait for that future to finish
264 // before continuing.
265 if (printer_state.print_is_pending()) {
266 std::cout << "waiting for async host function to complete" << std::endl;
267 printer_state.wait_for_print_result();
268 std::cout << "async host function completed" << std::endl;
269 continue;
270 }
271 // Otherwise we ran out of fuel and yielded.
272 std::cout << "yield!" << std::endl;
273 }
274 // Extract if there were failures or traps after poll returns that execution
275 // completed.
276 error.reset(error_ptr);
277 trap.reset(trap_ptr);
278 if (error || trap) {
279 exit_with_error("running guest function failed", error.get(), trap.get());
280 }
281 call_future = nullptr;
282 // At this point, if our host function returned results they would be
283 // available in the `results` array.
284 std::cout << "async function call complete!" << std::endl;
285
286 // Join our thread and exit.
287 printer_thread.join();
288 return 0;
289 }
290