-
Notifications
You must be signed in to change notification settings - Fork 170
/
async.mjs
74 lines (67 loc) · 2.61 KB
/
async.mjs
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
let cur_await_promise_sync;
export function await_promise_sync(promise_addr, result_addr, error_addr) {
cur_await_promise_sync(promise_addr, result_addr, error_addr);
}
const State = {
None: 0,
Unwinding: 1,
Rewinding: 2
};
// This uses Binaryen's Asyncify transform to suspend native code execution while a promise is resolving.
// That allows synchronous Rust code to call async JavaScript functions without multi-threading.
// When Rust wants to await a promise, it calls await_promise_sync, which saves the stack state and unwinds.
// That causes the bundle function to return early. If a promise has been queued, we can then await it
// and "rewind" the function back to where it was before by calling it again. This time the result of
// the promise can be returned, and the function can continue where it left off.
// See the docs in https://github.com/WebAssembly/binaryen/blob/main/src/passes/Asyncify.cpp
// The code here is also partially based on https://github.com/GoogleChromeLabs/asyncify
export function createBundleAsync(env) {
let {instance, exports} = env;
let {asyncify_get_state, asyncify_start_unwind, asyncify_stop_unwind, asyncify_start_rewind, asyncify_stop_rewind} = instance.exports;
// allocate __asyncify_data
// Stack data goes right after the initial descriptor.
let DATA_ADDR = instance.exports.napi_wasm_malloc(8 + 4096);
let DATA_START = DATA_ADDR + 8;
let DATA_END = DATA_ADDR + 8 + 4096;
new Int32Array(env.memory.buffer, DATA_ADDR).set([DATA_START, DATA_END]);
function assertNoneState() {
if (asyncify_get_state() !== State.None) {
throw new Error(`Invalid async state ${asyncify_get_state()}, expected 0.`);
}
}
let promise, result, error;
cur_await_promise_sync = (promise_addr, result_addr, error_addr) => {
let state = asyncify_get_state();
if (state === State.Rewinding) {
asyncify_stop_rewind();
if (result != null) {
env.createValue(result, result_addr);
}
if (error != null) {
env.createValue(error, error_addr);
}
promise = result = error = null;
return;
}
assertNoneState();
promise = env.get(promise_addr);
asyncify_start_unwind(DATA_ADDR);
};
return async function bundleAsync(options) {
assertNoneState();
let res = exports.bundle(options);
while (asyncify_get_state() === State.Unwinding) {
asyncify_stop_unwind();
try {
result = await promise;
} catch (err) {
error = err;
}
assertNoneState();
asyncify_start_rewind(DATA_ADDR);
res = exports.bundle(options);
}
assertNoneState();
return res;
};
}