Skip to content

Commit 116b60f

Browse files
authored
Replace old fastcomp asyncify docs with docs for the new wasm backend (#9017)
1 parent 391fc14 commit 116b60f

File tree

1 file changed

+177
-78
lines changed

1 file changed

+177
-78
lines changed

site/source/docs/porting/asyncify.rst

+177-78
Original file line numberDiff line numberDiff line change
@@ -4,123 +4,222 @@
44
Asyncify
55
========================
66

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:
89

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.
1014

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.
1219

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.
1424

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!
1730

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+
#####################################
1933

20-
::
34+
Let's begin with the example from that blogpost:
35+
36+
.. code-block:: cpp
2137
22-
// test.c
38+
// example.cpp
39+
#include <emscripten.h>
2340
#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+
2555
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+
}
3166
}
3267
33-
Note that we cannot implement ``sleep`` like this::
68+
You can compile that with
3469

35-
function sleep(ms) {
36-
var t = Date.now() + ms;
37-
while(Date.now() < t) ;
38-
}
70+
::
3971

40-
because this would block the JavaScript engine, such that pending events cannot be processed.
72+
emcc -O3 example.cpp -s ASYNCIFY
4173

74+
.. note:: It's very important to optimize (``-O3`` here) when using Asyncify, as
75+
unoptimized builds are very large.
4276

43-
A hand written counterpart in JavaScript would be
77+
And you can run it with
4478

4579
::
4680

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
5582

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:
6484

65-
And the ``ASYNCIFY`` option does all above automatically, through a number of transformations on LLVM IR.
85+
::
6686

87+
sleeping...
88+
sleeping...
89+
sleeping...
90+
sleeping...
91+
sleeping...
92+
timer happened!
6793

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!
7098

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+
########################################################
72101

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:
74107

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
76109
77-
Extensions
78-
==========
110+
// example.c
111+
#include <emscripten.h>
112+
#include <stdio.h>
79113
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+
});
81124
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+
}
86130
87-
Please read ``src/library_async.js`` for details.
88131
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.
91136

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!
95140

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
101142

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"]'
103146

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.
105150

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:
107154

108155
::
109156

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.
117214

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+
#########################
119217

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:
120220

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.
123225

124-
- Closures (breaking asm.js)
125-
- Generators (too slow currently)
126-
- Blocking message (in workers)

0 commit comments

Comments
 (0)