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_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> 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> 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> 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: 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 } 123 int32_t get_value_to_print() { return _value_to_print.get_future().get(); } 124 125 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 } 130 void wait_for_print_result() const { _print_finished_future.wait(); } 131 void get_print_result() { _print_finished_future.get(); } 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 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 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