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 { 42 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 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 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_async_support_set(config, true); 65 wasmtime_config_consume_fuel_set(config, true); 66 handle<wasm_engine_t, wasm_engine_delete> engine; 67 // this takes ownership of config 68 engine.reset(wasm_engine_new_with_config(config)); 69 assert(engine); 70 return engine; 71 } 72 73 handle<wasmtime_store_t, wasmtime_store_delete> 74 create_store(wasm_engine_t *engine) { 75 handle<wasmtime_store_t, wasmtime_store_delete> store; 76 store.reset(wasmtime_store_new(engine, nullptr, nullptr)); 77 assert(store); 78 return store; 79 } 80 81 handle<wasmtime_linker_t, wasmtime_linker_delete> 82 create_linker(wasm_engine_t *engine) { 83 handle<wasmtime_linker_t, wasmtime_linker_delete> linker; 84 linker.reset(wasmtime_linker_new(engine)); 85 assert(linker); 86 return linker; 87 } 88 89 handle<wasmtime_module_t, wasmtime_module_delete> 90 compile_wat_module_from_file(wasm_engine_t *engine, 91 const std::string &filename) { 92 std::ifstream t(filename); 93 std::stringstream buffer; 94 buffer << t.rdbuf(); 95 if (t.bad()) { 96 std::cerr << "error reading file: " << filename << std::endl; 97 std::exit(1); 98 } 99 const std::string &content = buffer.str(); 100 wasm_byte_vec_t wasm_bytes; 101 handle<wasmtime_error_t, wasmtime_error_delete> error{ 102 wasmtime_wat2wasm(content.data(), content.size(), &wasm_bytes)}; 103 if (error) { 104 exit_with_error("failed to parse wat", error.get(), nullptr); 105 } 106 wasmtime_module_t *mod_ptr = nullptr; 107 error.reset(wasmtime_module_new(engine, 108 reinterpret_cast<uint8_t *>(wasm_bytes.data), 109 wasm_bytes.size, &mod_ptr)); 110 wasm_byte_vec_delete(&wasm_bytes); 111 handle<wasmtime_module_t, wasmtime_module_delete> mod{mod_ptr}; 112 if (!mod) { 113 exit_with_error("failed to compile module", error.get(), nullptr); 114 } 115 return mod; 116 } 117 118 class printer_thread_state { 119 public: 120 void set_value_to_print(int32_t v) { 121 _print_finished_future = _print_finished.get_future(); 122 _value_to_print.set_value(v); 123 } 124 int32_t get_value_to_print() { return _value_to_print.get_future().get(); } 125 126 bool print_is_pending() const { 127 return _print_finished_future.valid() && 128 _print_finished_future.wait_for(std::chrono::seconds(0)) != 129 std::future_status::ready; 130 } 131 void wait_for_print_result() const { _print_finished_future.wait(); } 132 void get_print_result() { _print_finished_future.get(); } 133 void set_print_success() { _print_finished.set_value(); } 134 135 private: 136 std::promise<int32_t> _value_to_print; 137 std::promise<void> _print_finished; 138 std::future<void> _print_finished_future; 139 }; 140 141 printer_thread_state printer_state; 142 143 struct async_call_env { 144 wasm_trap_t **trap_ret; 145 }; 146 147 bool poll_print_finished_state(void *env) { 148 std::cout << "polling async host function result" << std::endl; 149 auto *async_env = static_cast<async_call_env *>(env); 150 // Don't block, just poll the future state. 151 if (printer_state.print_is_pending()) { 152 return false; 153 } 154 try { 155 printer_state.get_print_result(); 156 } catch (const std::exception &ex) { 157 std::string msg = ex.what(); 158 *async_env->trap_ret = wasmtime_trap_new(msg.data(), msg.size()); 159 } 160 return true; 161 } 162 } // namespace 163 164 int main() { 165 // A thread that will async perform host function calls. 166 std::thread printer_thread([]() { 167 int32_t value_to_print = printer_state.get_value_to_print(); 168 std::cout << "received value to print!" << std::endl; 169 std::this_thread::sleep_for(std::chrono::milliseconds(500)); 170 std::cout << "printing: " << value_to_print << std::endl; 171 std::this_thread::sleep_for(std::chrono::milliseconds(500)); 172 std::cout << "signaling that value is printed" << std::endl; 173 printer_state.set_print_success(); 174 }); 175 176 handle<wasmtime_error_t, wasmtime_error_delete> error; 177 178 auto engine = create_engine(); 179 auto store = create_store(engine.get()); 180 // This pointer is unowned. 181 auto *context = wasmtime_store_context(store.get()); 182 // Configure the store to periodically yield control 183 wasmtime_context_set_fuel(context, 100000); 184 wasmtime_context_fuel_async_yield_interval(context, /*interval=*/10000); 185 186 auto compiled_module = 187 compile_wat_module_from_file(engine.get(), "examples/async.wat"); 188 189 auto linker = create_linker(engine.get()); 190 static std::string host_module_name = "host"; 191 static std::string host_func_name = "print"; 192 193 // Declare our async host function's signature and definition. 194 wasm_valtype_vec_t arg_types; 195 wasm_valtype_vec_t result_types; 196 wasm_valtype_vec_new_uninitialized(&arg_types, 1); 197 arg_types.data[0] = wasm_valtype_new_i32(); 198 wasm_valtype_vec_new_empty(&result_types); 199 handle<wasm_functype_t, wasm_functype_delete> functype{ 200 wasm_functype_new(&arg_types, &result_types)}; 201 202 error.reset(wasmtime_linker_define_async_func( 203 linker.get(), host_module_name.data(), host_module_name.size(), 204 host_func_name.data(), host_func_name.size(), functype.get(), 205 [](void *, wasmtime_caller_t *, const wasmtime_val_t *args, size_t, 206 wasmtime_val_t *, size_t, wasm_trap_t **trap_ret, 207 wasmtime_async_continuation_t *continuation_ret) { 208 std::cout << "invoking async host function" << std::endl; 209 printer_state.set_value_to_print(args[0].of.i32); 210 211 continuation_ret->callback = &poll_print_finished_state; 212 continuation_ret->env = new async_call_env{trap_ret}; 213 continuation_ret->finalizer = [](void *env) { 214 std::cout << "deleting async_call_env" << std::endl; 215 delete static_cast<async_call_env *>(env); 216 }; 217 }, 218 /*env=*/nullptr, /*finalizer=*/nullptr)); 219 if (error) { 220 exit_with_error("failed to define host function", error.get(), nullptr); 221 } 222 223 // Now instantiate our module using the linker. 224 handle<wasmtime_call_future_t, wasmtime_call_future_delete> call_future; 225 wasm_trap_t *trap_ptr = nullptr; 226 wasmtime_error_t *error_ptr = nullptr; 227 wasmtime_instance_t instance; 228 call_future.reset(wasmtime_linker_instantiate_async( 229 linker.get(), context, compiled_module.get(), &instance, &trap_ptr, 230 &error_ptr)); 231 while (!wasmtime_call_future_poll(call_future.get())) { 232 std::cout << "yielding instantiation!" << std::endl; 233 } 234 error.reset(error_ptr); 235 handle<wasm_trap_t, wasm_trap_delete> trap{trap_ptr}; 236 if (error || trap) { 237 exit_with_error("failed to instantiate module", error.get(), trap.get()); 238 } 239 // delete call future - it's no longer needed 240 call_future = nullptr; 241 // delete the linker now that we've created our instance 242 linker = nullptr; 243 244 // Grab our exported function 245 static std::string guest_func_name = "print_fibonacci"; 246 wasmtime_extern_t guest_func_extern; 247 bool found = 248 wasmtime_instance_export_get(context, &instance, guest_func_name.data(), 249 guest_func_name.size(), &guest_func_extern); 250 assert(found); 251 assert(guest_func_extern.kind == WASMTIME_EXTERN_FUNC); 252 253 // Now call our print_fibonacci function with n=15 254 std::array<wasmtime_val_t, 1> args; 255 args[0].kind = WASMTIME_I32; 256 args[0].of.i32 = 15; 257 std::array<wasmtime_val_t, 0> results; 258 call_future.reset(wasmtime_func_call_async( 259 context, &guest_func_extern.of.func, args.data(), args.size(), 260 results.data(), results.size(), &trap_ptr, &error_ptr)); 261 // Poll the execution of the call. This can yield control back if there is an 262 // async host call or if we ran out of fuel. 263 while (!wasmtime_call_future_poll(call_future.get())) { 264 // if we have an async host call pending then wait for that future to finish 265 // before continuing. 266 if (printer_state.print_is_pending()) { 267 std::cout << "waiting for async host function to complete" << std::endl; 268 printer_state.wait_for_print_result(); 269 std::cout << "async host function completed" << std::endl; 270 continue; 271 } 272 // Otherwise we ran out of fuel and yielded. 273 std::cout << "yield!" << std::endl; 274 } 275 // Extract if there were failures or traps after poll returns that execution 276 // completed. 277 error.reset(error_ptr); 278 trap.reset(trap_ptr); 279 if (error || trap) { 280 exit_with_error("running guest function failed", error.get(), trap.get()); 281 } 282 call_future = nullptr; 283 // At this point, if our host function returned results they would be 284 // available in the `results` array. 285 std::cout << "async function call complete!" << std::endl; 286 287 // Join our thread and exit. 288 printer_thread.join(); 289 return 0; 290 } 291