|
4 | 4 | Asyncify
|
5 | 5 | ========================
|
6 | 6 |
|
7 |
| -In general, you want to make your code as asynchronous-friendly as possible, e.g. by never calling sleep, having a single main loop and so on. |
| 7 | +Asyncify lets **synchronous** C or C++ code interact with **asynchronous** |
| 8 | +JavaScript. This allows things like: |
8 | 9 |
|
9 |
| -Sometimes refactoring a codebase with this in mind isn't feasible, so there are a couple of alternatives (each with downsides). Asyncify is covered here. |
| 10 | + * A synchronous call in C that yields to the event loop, which |
| 11 | + allows browser events to be handled. |
| 12 | + * A synchronous call in C that waits for an asynchronous operation in JS to |
| 13 | + complete. |
10 | 14 |
|
11 |
| -**Asyncify is experimental, and not recommended. See https://kripken.github.io/emscripten-site/docs/porting/emterpreter.html for a more recent option with similar functionality, that is currently supported.** |
| 15 | +Asyncify automatically transforms your compiled code into a form that can be |
| 16 | +paused and resumed, and handles pausing and resuming for you, so that it is |
| 17 | +asynchronous (hence the name "Asyncify") even though you wrote it in a normal |
| 18 | +synchronous way. |
12 | 19 |
|
13 |
| -``ASYNCIFY`` allows you to use some asynchronous function in C, through several transformation of LLVM IR. |
| 20 | +See the |
| 21 | +`Asyncify introduction blogpost <https://kripken.github.io/blog/wasm/2019/07/16/asyncify.html>`_ |
| 22 | +for general background and details of how it works internally. The following |
| 23 | +expands on the Emscripten examples from that post. |
14 | 24 |
|
15 |
| -Intro |
16 |
| -===== |
| 25 | +.. note:: This post talks about Asyncify using the new LLVM wasm backend. |
| 26 | + There is also an older Asyncify implementation for the old fastcomp |
| 27 | + backend. The two algorithms and implementations are entirely separate, |
| 28 | + so if you are using fastcomp, these docs may not be accurate - you |
| 29 | + should upgrade to the wasm backend and new Asyncify! |
17 | 30 |
|
18 |
| -If you call ``sleep()`` in C/C++, what kind of JavaScript code would you expect emscripten to produce? |
| 31 | +Sleeping / yielding to the event loop |
| 32 | +##################################### |
19 | 33 |
|
20 |
| -:: |
| 34 | +Let's begin with the example from that blogpost: |
| 35 | + |
| 36 | +.. code-block:: cpp |
21 | 37 |
|
22 |
| - // test.c |
| 38 | + // example.cpp |
| 39 | + #include <emscripten.h> |
23 | 40 | #include <stdio.h>
|
24 |
| - #include <unistd.h> |
| 41 | +
|
| 42 | + // start_timer(): call JS to set an async timer for 500ms |
| 43 | + EM_JS(void, start_timer, (), { |
| 44 | + Module.timer = false; |
| 45 | + setTimeout(function() { |
| 46 | + Module.timer = true; |
| 47 | + }, 500); |
| 48 | + }); |
| 49 | +
|
| 50 | + // check_timer(): check if that timer occurred |
| 51 | + EM_JS(bool, check_timer, (), { |
| 52 | + return Module.timer; |
| 53 | + }); |
| 54 | +
|
25 | 55 | int main() {
|
26 |
| - int i = 100; |
27 |
| - printf("Hello\n"); |
28 |
| - sleep(1); |
29 |
| - printf("World %d!\n"); |
30 |
| - return 0; |
| 56 | + start_timer(); |
| 57 | + // Continuously loop while synchronously polling for the timer. |
| 58 | + while (1) { |
| 59 | + if (check_timer()) { |
| 60 | + printf("timer happened!\n"); |
| 61 | + return 0; |
| 62 | + } |
| 63 | + printf("sleeping...\n"); |
| 64 | + emscripten_sleep(100); |
| 65 | + } |
31 | 66 | }
|
32 | 67 |
|
33 |
| -Note that we cannot implement ``sleep`` like this:: |
| 68 | +You can compile that with |
34 | 69 |
|
35 |
| - function sleep(ms) { |
36 |
| - var t = Date.now() + ms; |
37 |
| - while(Date.now() < t) ; |
38 |
| - } |
| 70 | +:: |
39 | 71 |
|
40 |
| -because this would block the JavaScript engine, such that pending events cannot be processed. |
| 72 | + emcc -O3 example.cpp -s ASYNCIFY |
41 | 73 |
|
| 74 | +.. note:: It's very important to optimize (``-O3`` here) when using Asyncify, as |
| 75 | + unoptimized builds are very large. |
42 | 76 |
|
43 |
| -A hand written counterpart in JavaScript would be |
| 77 | +And you can run it with |
44 | 78 |
|
45 | 79 | ::
|
46 | 80 |
|
47 |
| - function main() { |
48 |
| - var i = 100; |
49 |
| - console.log('Hello'); |
50 |
| - setTimeout(function() { |
51 |
| - console.log('World ' + i + '!'); |
52 |
| - async_return_value = 0; |
53 |
| - }, 1000); |
54 |
| - } |
| 81 | + nodejs a.out.js |
55 | 82 |
|
56 |
| -Specifically, a number of aspects should be taken into consideration: |
57 |
| - - Split the function when an async function is called, and the second function should be registered as the callback for the async function |
58 |
| - - Any function that calls an async function also becomes an async function. |
59 |
| - - Keep all local variables available to the callback |
60 |
| - - Closure cannot be used in order to make asm.js validated code. |
61 |
| - - Take care of loops and branches |
62 |
| - - Make the return value available to the callee |
63 |
| - - Some functions could be both sync or async, depending on the input. |
| 83 | +You should then see something like this: |
64 | 84 |
|
65 |
| -And the ``ASYNCIFY`` option does all above automatically, through a number of transformations on LLVM IR. |
| 85 | +:: |
66 | 86 |
|
| 87 | + sleeping... |
| 88 | + sleeping... |
| 89 | + sleeping... |
| 90 | + sleeping... |
| 91 | + sleeping... |
| 92 | + timer happened! |
67 | 93 |
|
68 |
| -Usage |
69 |
| -===== |
| 94 | +The code is written with a straightforward loop, which does not exit while |
| 95 | +it is running, which normally would not allow async events to be handled by the |
| 96 | +browser. With Asyncify, those sleeps actually yield to the browser's main event |
| 97 | +loop, and the timer can happen! |
70 | 98 |
|
71 |
| -Call ``emscripten_sleep()`` whenever you need to pause the program, and add ``-s ASYNCIFY=1`` to emscripten. |
| 99 | +Making async Web APIs behave as if they were synchronous |
| 100 | +######################################################## |
72 | 101 |
|
73 |
| -Sometimes it's a good replacement of ``emscripten_set_main_loop``, you may replace all ``sleep``-alike functions with ``emscripten_sleep``, instead of refactoring the whole main loop. |
| 102 | +Aside from ``emscripten_sleep`` and the other standard sync APIs Asyncify |
| 103 | +supports, you can also add your own functions. To do so, you must create a JS |
| 104 | +function that is called from wasm (since Emscripten controls pausing and |
| 105 | +resuming the wasm from the JS runtime). One way to do that is with a JS library |
| 106 | +function; another is to use ``EM_JS``, which we'll use in this next example: |
74 | 107 |
|
75 |
| -Also ``emscripten_sleep(1)`` can be used to 'interrupt' your code, such that the JavaScript engine can do the rendering and process events. |
| 108 | +.. code-block:: cpp |
76 | 109 |
|
77 |
| -Extensions |
78 |
| -========== |
| 110 | + // example.c |
| 111 | + #include <emscripten.h> |
| 112 | + #include <stdio.h> |
79 | 113 |
|
80 |
| -It is possible to implement more new async functions that appears to be sync in C. |
| 114 | + EM_JS(void, do_fetch, (), { |
| 115 | + Asyncify.handleSleep(function(wakeUp) { |
| 116 | + out("waiting for a fetch"); |
| 117 | + fetch("a.html").then(response => { |
| 118 | + out("got the fetch response"); |
| 119 | + // (normally you would do something with the fetch here) |
| 120 | + wakeUp(); |
| 121 | + }); |
| 122 | + }); |
| 123 | + }); |
81 | 124 |
|
82 |
| -- Implement the function normally in a JavaScript library, suppose the function name is ``func``. |
83 |
| -- Add ``func`` into ``ASYNCIFY_FUNCTIONS`` |
84 |
| -- When ``func`` is called and finished, the program will NOT continue, instead it just save the context and exit. |
85 |
| -- Call ``_emscripten_async_resume`` when you want to resume the program, usually in the callback functions of some async calls. |
| 125 | + int main() { |
| 126 | + puts("before"); |
| 127 | + do_fetch(); |
| 128 | + puts("after"); |
| 129 | + } |
86 | 130 |
|
87 |
| -Please read ``src/library_async.js`` for details. |
88 | 131 |
|
89 |
| -Limitations |
90 |
| -=========== |
| 132 | +The async operation happens in the ``EM_JS`` function ``do_fetch()``, which |
| 133 | +calls ``Asyncify.handleSleep``. It gives that function the code to be run, and |
| 134 | +gets a ``wakeUp`` function that it calls in the asynchronous future at the right |
| 135 | +time. After we call ``wakeUp()`` the compiled C code resumes normally. |
91 | 136 |
|
92 |
| -Code size increase should be expected, depending on the specific input. |
93 |
| -``-Os`` (or ``-Oz`` for linking) is recommended when ``ASYNCIFY`` is turned on. |
94 |
| -E.g. usually the following loop is expanded to speed up:: |
| 137 | +In this example the async operation is a ``fetch``, which means we need to wait |
| 138 | +for a Promise. While that is async, note how the C code in ``main()`` is |
| 139 | +completely synchronous! |
95 | 140 |
|
96 |
| - for(int i = 0; i < 3; ++i) { |
97 |
| - // do something |
98 |
| - emscripten_sleep(1000); |
99 |
| - // do something else |
100 |
| - } |
| 141 | +To run this example, first compile it with |
101 | 142 |
|
102 |
| -However by expanding the loop, two more async calls are introduced, such that more callback functions will be produced during the asyncify transformation. |
| 143 | +:: |
| 144 | + |
| 145 | + ./emcc example.c -O3 -o a.html -s ASYNCIFY -s 'ASYNCIFY_IMPORTS=["do_fetch"]' |
103 | 146 |
|
104 |
| -**Asyncify can make performance much slower, if it ends up splitting a function which you need to be fast.** |
| 147 | +Note that you must tell the compiler that ``do_fetch()`` can do an |
| 148 | +asynchronous operation, using ``ASYNCIFY_IMPORTS``, otherwise it won't |
| 149 | +instrument the code to allow pausing and resuming; see more details later down. |
105 | 150 |
|
106 |
| -``setjmp/longjmp`` and C++ exception are not working well when there are async function calls in the scope, but they still work when there's no async calls. E.g. |
| 151 | +To run this, you must run a webserver (like say ``python -m SimpleHTTPServer``) |
| 152 | +and then browse to ``http://localhost:8000/a.html`` (the URL may depend on the |
| 153 | +port number in the server). You will see something like this: |
107 | 154 |
|
108 | 155 | ::
|
109 | 156 |
|
110 |
| - try { |
111 |
| - // do something |
112 |
| - if(error) throw 0; // works |
113 |
| - emscripten_sleep(1000); |
114 |
| - // do something else |
115 |
| - if(error) throw 0; // does not work |
116 |
| - } |
| 157 | + before |
| 158 | + waiting for a fetch |
| 159 | + got the fetch response |
| 160 | + after |
| 161 | + |
| 162 | +That shows that the C code only continued to execute after the async JS |
| 163 | +completed. |
| 164 | + |
| 165 | +More on ``ASYNCIFY_IMPORTS`` |
| 166 | +############################ |
| 167 | + |
| 168 | +As in the above example, you can add JS functions that do an async operation but |
| 169 | +look synchronous from the perspective of C. The key thing is to add such methods |
| 170 | +to ``ASYNCIFY_IMPORTS``, regardless of whether the JS function is from a JS |
| 171 | +library or ``EM_JS``. That list of imports is the list of imports to the wasm |
| 172 | +module that the Asyncify instrumentation must be aware of. Giving it that list |
| 173 | +tells it that all other JS calls will **not** do an async operation, which lets |
| 174 | +it not add overhead where it isn't needed. |
| 175 | + |
| 176 | +The ``ASYNCIFY_IMPORTS`` list must contain **all** relevant imports, not just |
| 177 | +ones you add yourself, so it must contain things like ``emscripten_sleep()`` |
| 178 | +if you call them (by default the list will contain them, so you must only add |
| 179 | +them if you change the list). |
| 180 | + |
| 181 | +You can also set ``ASYNCIFY_IMPORTS`` to ``[]`` (an empty list). In that case, |
| 182 | +Asyncify will assume that any import may do an async operation. This will result |
| 183 | +in larger and slower code in most cases, but can be useful during debugging or |
| 184 | +development. |
| 185 | + |
| 186 | +Potential problems |
| 187 | +################## |
| 188 | + |
| 189 | +Stack overflows |
| 190 | +*************** |
| 191 | + |
| 192 | +If you see an exception thrown from an ``asyncify_*`` API, then it may be |
| 193 | +a stack overflow. You can increase the stack size with the |
| 194 | +``ASYNCIFY_STACK_SIZE`` option. |
| 195 | + |
| 196 | +Reentrancy |
| 197 | +********** |
| 198 | + |
| 199 | +While waiting on an asynchronous operation browser events can happen. That |
| 200 | +is often the point of using Asyncify, but unexpected events can happen too. |
| 201 | +For example, if you just want to pause for 100ms then you can call |
| 202 | +``emscripten_sleep(100)``, but if you have any event listeners, say for a |
| 203 | +keypress, then if a key is pressed the handler will fire. If that handler |
| 204 | +calls into compiled code, then it can be confusing, since it starts to look |
| 205 | +like coroutines or multithreading, with multiple executions interleaved. |
| 206 | + |
| 207 | +It is *not* safe to start an async operation while another is already running. |
| 208 | +The first must complete before the second begins. |
| 209 | + |
| 210 | +Such interleaving may also break assumptions in your codebase. For example, |
| 211 | +if a function uses a global and assumes nothing else can modify it until it |
| 212 | +returns, but if that function sleeps and an event causes other code to |
| 213 | +change that global, then bad things can happen. |
117 | 214 |
|
118 |
| -Currently all function pointer calls are considered as aync, and some functions might be recognized as async incorrectly. This can be corrected by manually setting the ``ASYNCIFY_WHITELIST`` option. |
| 215 | +Migrating from older APIs |
| 216 | +######################### |
119 | 217 |
|
| 218 | +If you have code using the Emterpreter-Async API, or the old Asyncify, then the |
| 219 | +new API is somewhat different, and you may need some minor changes: |
120 | 220 |
|
121 |
| -Other possible implementations |
122 |
| -============================== |
| 221 | + * The Emterpreter has "yielding" as a concept, but it isn't needed in Asyncify. |
| 222 | + You can replace ``emscripten_sleep_with_yield()`` calls with ``emscripten_sleep()``. |
| 223 | + * The JS API is different. See notes above on ``Asyncify.handleSleep()``, and |
| 224 | + see ``src/library_async.js`` for more examples. |
123 | 225 |
|
124 |
| - - Closures (breaking asm.js) |
125 |
| - - Generators (too slow currently) |
126 |
| - - Blocking message (in workers) |
|
0 commit comments