|
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