Skip to content

Commit fda71e5

Browse files
authored
[ASYNCIFY] Document new __async decorator. NFC (emscripten-core#26130)
1 parent 544bcaf commit fda71e5

2 files changed

Lines changed: 72 additions & 30 deletions

File tree

ChangeLog.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,9 @@ See docs/process.md for more on how version tagging works.
3131
- Embind now supports the JS iterable protocol on bound classes via
3232
`class_<T>::iterable()`. `register_vector` uses this so bound `std::vector`
3333
works with `for...of`/`Array.from()`/spread. (#25993)
34+
- ASYNCIFY/JSPI functions in JS library files can now be marked as `__async:
35+
'auto'`, which allows async JS function to be used unmodified with
36+
ASYNCIFY/JSPI. (#26130, #26019)
3437

3538
4.0.23 - 01/10/26
3639
-----------------

site/source/docs/porting/asyncify.rst

Lines changed: 69 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ C++ code interact with **asynchronous** JavaScript. This allows things like:
1212
* A synchronous call in C that waits for an asynchronous operation in JS to
1313
complete.
1414

15-
1615
In general the two options are very similar, but rely on different underlying
1716
mechanisms to work.
1817

@@ -162,6 +161,45 @@ You will see something like this:
162161
That shows that the C code only continued to execute after the async JS
163162
completed.
164163

164+
Marking JS library functions as async
165+
#####################################
166+
167+
If you mark a JS library function as async using the ``__async`` decorator then
168+
the compiler will take a care of all the details of using the ``Asyncify`` API
169+
for you. The function will also automatically be incldued in
170+
:ref:`ASYNCIFY_IMPORTS`. All you need to do is write normal async JS function
171+
(either using the explict ``async`` JS keyword or returning a ``Promise``
172+
object). For example:
173+
174+
.. code-block:: js
175+
176+
addToLibrary({
177+
fetch_v1__async: 'auto',
178+
fetch_v2: async (url) => {
179+
const response = await fetch(UTF8ToString(url);
180+
const json_data = await response.json();
181+
return stringToNewUTF8(json_data);
182+
},
183+
184+
fetch_v2__async: 'auto',
185+
fetch_v2: (url) => {
186+
return fetch(UTF8ToString(url))
187+
.then((rsp) => response.json())
188+
.then((json_data) => stringToNewUTF8(json_data));
189+
},
190+
});
191+
192+
This JS library file contains two JS functions (``fetch_v1`` and ``fetch_v2``)
193+
which do exactly the same thing, one using the JS ``async`` keyword and one
194+
using promise chaining. They are both marked as ``__async: 'auto'`` which means
195+
they will automatically suspend Wasm executation when called and resume when the
196+
resulting promise resolves.
197+
198+
You can use ``__async: 1`` if you just want to include the function in
199+
:ref:`ASYNCIFY_IMPORTS` or ``__async: 'auto'`` if you also want the function to
200+
wrapper in ``Asyncify.handleAsync``.
201+
202+
165203
Ways to use Asyncify APIs in older engines
166204
##########################################
167205
@@ -184,8 +222,9 @@ directly with ``EM_JS`` and ``Asyncify.handleAsync`` instead:
184222
185223
When using this form, the compiler doesn't statically know that ``do_fetch`` is
186224
asynchronous anymore. Instead, you must tell the compiler that ``do_fetch()``
187-
can do an asynchronous operation using ``ASYNCIFY_IMPORTS``, otherwise it won't
188-
instrument the code to allow pausing and resuming (see more details later down):
225+
can do an asynchronous operation using :ref:`ASYNCIFY_IMPORTS`, otherwise it
226+
won't instrument the code to allow pausing and resuming (see more details later
227+
down):
189228
190229
::
191230
@@ -219,10 +258,10 @@ More on ``ASYNCIFY_IMPORTS``
219258
220259
As in the above example, you can add JS functions that do an async operation but
221260
look synchronous from the perspective of C. If you don't use ``EM_ASYNC_JS``,
222-
it's vital to add such methods to ``ASYNCIFY_IMPORTS``. That list of imports is
223-
the list of imports to the Wasm module that the Asyncify instrumentation must be
224-
aware of. Giving it that list tells it that all other JS calls will **not** do
225-
an async operation, which lets it not add overhead where it isn't needed.
261+
it's vital to add such methods to :ref:`ASYNCIFY_IMPORTS`. That list of imports
262+
is the list of imports to the Wasm module that the Asyncify instrumentation must
263+
be aware of. Giving it that list tells it that all other JS calls will **not**
264+
do an async operation, which lets it not add overhead where it isn't needed.
226265
227266
.. note:: If the import is not inside ``env`` the full path must be specified, for example, ``ASYNCIFY_IMPORTS=wasi_snapshot_preview1.fd_write``
228267
@@ -231,7 +270,7 @@ Asyncify with Dynamic Linking
231270
232271
If you want to use Asyncify in dynamic libraries, those methods which are imported
233272
from other linked modules (and that will be on the stack in an async operation)
234-
should be listed in ``ASYNCIFY_IMPORTS``.
273+
should be listed in :ref:`ASYNCIFY_IMPORTS`.
235274
236275
.. code-block:: cpp
237276
@@ -262,8 +301,8 @@ linking manner:
262301
}
263302
264303
In the main module, the compiler doesn’t statically know that ``sleep_for_seconds`` is
265-
asynchronous. Therefore, you must add ``sleep_for_seconds`` to the ``ASYNCIFY_IMPORTS``
266-
list.
304+
asynchronous. Therefore, you must add ``sleep_for_seconds`` to the
305+
:ref:`ASYNCIFY_IMPORTS` list.
267306
268307
::
269308
@@ -281,9 +320,9 @@ and want to ``await`` a dynamically retrieved ``Promise``, you can call an
281320
val my_object = /* ... */;
282321
val result = my_object.call<val>("someAsyncMethod").await();
283322
284-
In this case you don't need to worry about ``ASYNCIFY_IMPORTS`` or
285-
``JSPI_IMPORTS``, since it's an internal implementation detail of ``val::await``
286-
and Emscripten takes care of it automatically.
323+
In this case you don't need to worry about :ref:`ASYNCIFY_IMPORTS` or
324+
:ref:`JSPI_IMPORTS`, since it's an internal implementation detail of
325+
``val::await`` and Emscripten takes care of it automatically.
287326
288327
Note that when using Embind exports, Asyncify and JSPI behave differently. When
289328
Asyncify is used with Embind and the code is invoked from JavaScript, then the
@@ -373,9 +412,9 @@ Differences Between Asyncify and JSPI
373412
Besides using different underlying mechanisms, Asyncify and JSPI also handle
374413
async imports and exports differently. Asyncify will automatically determine
375414
what exports will become async based on what could potentially call an
376-
an async import (``ASYNCIFY_IMPORTS``). However, with JSPI, the async imports
377-
and exports must be explicitly set using ``JSPI_IMPORTS`` and ``JSPI_EXPORTS``
378-
settings.
415+
an async import (:ref:`ASYNCIFY_IMPORTS`). However, with JSPI, the async imports
416+
and exports must be explicitly set using :ref:`JSPI_IMPORTS` and
417+
:ref:`JSPI_EXPORTS` settings.
379418
380419
.. note:: ``<JSPI/ASYNCIFY>_IMPORTS`` and ``JSPI_EXPORTS`` aren't needed when
381420
using various helpers mentioned above such as: ``EM_ASYNC_JS``,
@@ -394,39 +433,39 @@ code to allow unwinding and rewinding. That overhead is usually not extreme,
394433
something like 50% or so. Asyncify achieves that by doing a whole-program
395434
analysis to find functions need to be instrumented and which do not -
396435
basically, which can call something that reaches one of
397-
``ASYNCIFY_IMPORTS``. That analysis avoids a lot of unnecessary overhead,
436+
:ref:`ASYNCIFY_IMPORTS`. That analysis avoids a lot of unnecessary overhead,
398437
however, it is limited by **indirect calls**, since it can't tell where
399438
they go - it could be anything in the function table (with the same type).
400439
401440
If you know that indirect calls are never on the stack when unwinding, then
402441
you can tell Asyncify to ignore indirect calls using
403-
``ASYNCIFY_IGNORE_INDIRECT``.
442+
:ref:`ASYNCIFY_IGNORE_INDIRECT`.
404443
405444
If you know that some indirect calls matter and others do not, then you
406445
can provide a manual list of functions to Asyncify:
407446
408-
* ``ASYNCIFY_REMOVE`` is a list of functions that do not unwind the stack.
447+
* :ref:`ASYNCIFY_REMOVE` is a list of functions that do not unwind the stack.
409448
As Asyncify processes the call tree, functions in this list will be removed,
410449
and neither they nor their callers will be instrumented (unless their callers
411450
need to be instrumented for other reasons.)
412-
* ``ASYNCIFY_ADD`` is a list of functions that do unwind the stack, and will be
451+
* :ref:`ASYNCIFY_ADD` is a list of functions that do unwind the stack, and will be
413452
processed like the imports. This is mostly useful
414-
if you use ``ASYNCIFY_IGNORE_INDIRECT`` but want to also mark some additional
415-
functions that need to unwind. If the ``ASYNCIFY_PROPAGATE_ADD`` setting is
453+
if you use :ref:`ASYNCIFY_IGNORE_INDIRECT` but want to also mark some additional
454+
functions that need to unwind. If the :ref:`ASYNCIFY_PROPAGATE_ADD` setting is
416455
disabled however, then this list will only be added after the whole-program
417-
analysis. If ``ASYNCIFY_PROPAGATE_ADD`` is disabled then you must also add
456+
analysis. If :ref:`ASYNCIFY_PROPAGATE_ADD` is disabled then you must also add
418457
their callers, their callers' callers, and so on.
419-
* ``ASYNCIFY_ONLY`` is a list of the **only** functions that can unwind
458+
* :ref:`ASYNCIFY_ONLY` is a list of the **only** functions that can unwind
420459
the stack. Asyncify will instrument exactly those and no others.
421460
422-
You can enable the ``ASYNCIFY_ADVISE`` setting, which will tell the compiler to
461+
You can enable the :ref:`ASYNCIFY_ADVISE` setting, which will tell the compiler to
423462
output which functions it is currently instrumenting and why. You can then
424-
determine whether you should add any functions to ``ASYNCIFY_REMOVE`` or
425-
whether it would be safe to enable ``ASYNCIFY_IGNORE_INDIRECT``. Note that this
463+
determine whether you should add any functions to :ref:`ASYNCIFY_REMOVE` or
464+
whether it would be safe to enable :ref:`ASYNCIFY_IGNORE_INDIRECT`. Note that this
426465
phase of the compiler happens after many optimization phases, and several
427466
functions maybe be inlined already. To be safe, run it with `-O0`.
428467
429-
For more details see ``settings.js``. Note that the manual settings
468+
For more details see :ref:`settings-reference`. Note that the manual settings
430469
mentioned here are error-prone - if you don't get things exactly right,
431470
your application can break. If you don't absolutely need maximal performance,
432471
it's usually ok to use the defaults.
@@ -439,7 +478,7 @@ Stack overflows (Asyncify)
439478
440479
If you see an exception thrown from an ``asyncify_*`` API, then it may be
441480
a stack overflow. You can increase the stack size with the
442-
``ASYNCIFY_STACK_SIZE`` option.
481+
:ref:`ASYNCIFY_STACK_SIZE` option.
443482
444483
Reentrancy
445484
**********
@@ -467,7 +506,7 @@ The examples above show `wakeUp()` being called from JS (after a callback,
467506
typically), and without any compiled code on the stack. If there *were* compiled
468507
code on the stack, then that could interfere with properly rewinding and
469508
resuming execution, in confusing ways, and therefore an assertion will be
470-
thrown in a build with ``ASSERTIONS``.
509+
thrown in a build with :ref:`ASSERTIONS`.
471510
472511
(Specifically, the problem there is that while rewinding will work properly,
473512
if you later unwind again, that unwinding will also unwind through that extra

0 commit comments

Comments
 (0)