Skip to content

Commit 7f8dfa7

Browse files
authored
Asyncify: Add support for dynamic linking (#15893)
Fixes #15594 Before Asyncify wouldn't work properly in side modules, because the globals, __asyncify_state and __asyncify_data, are not synchronized between main-module and side-modules. With WebAssembly/binaryen#4427, __asyncify_state and __asyncify_data will be imported globals in the side modules.
1 parent d7892a9 commit 7f8dfa7

File tree

7 files changed

+148
-0
lines changed

7 files changed

+148
-0
lines changed

emcc.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -567,6 +567,8 @@ def get_binaryen_passes():
567567
passes += ['--fpcast-emu']
568568
if settings.ASYNCIFY == 1:
569569
passes += ['--asyncify']
570+
if settings.MAIN_MODULE or settings.SIDE_MODULE:
571+
passes += ['--pass-arg=asyncify-relocatable']
570572
if settings.ASSERTIONS:
571573
passes += ['--pass-arg=asyncify-asserts']
572574
if settings.ASYNCIFY_ADVISE:
@@ -1906,6 +1908,13 @@ def phase_linker_setup(options, state, newargs, user_settings):
19061908
'__heap_base',
19071909
'__stack_pointer',
19081910
]
1911+
1912+
if settings.ASYNCIFY:
1913+
settings.DEFAULT_LIBRARY_FUNCS_TO_INCLUDE += [
1914+
'__asyncify_state',
1915+
'__asyncify_data'
1916+
]
1917+
19091918
# Unconditional dependency in library_dylink.js
19101919
settings.REQUIRED_EXPORTS += ['setThrew']
19111920

emscripten.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -350,6 +350,9 @@ def emscript(in_wasm, out_wasm, outfile_js, memfile):
350350
if settings.INITIAL_TABLE == -1:
351351
settings.INITIAL_TABLE = dylink_sec.table_size + 1
352352

353+
if settings.ASYNCIFY:
354+
metadata['globalImports'] += ['__asyncify_state', '__asyncify_data']
355+
353356
invoke_funcs = metadata['invokeFuncs']
354357
if invoke_funcs:
355358
settings.DEFAULT_LIBRARY_FUNCS_TO_INCLUDE += ['$getWasmTableEntry']

site/source/docs/porting/asyncify.rst

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -210,6 +210,49 @@ the list of imports to the wasm module that the Asyncify instrumentation must be
210210
aware of. Giving it that list tells it that all other JS calls will **not** do
211211
an async operation, which lets it not add overhead where it isn't needed.
212212

213+
Asyncify with Dynamic Linking
214+
#############################
215+
216+
If you want to use Asyncify in dynamic libraries, those methods which are imported
217+
from other linked modules (and that will be on the stack in an async operation)
218+
should be listed in ``ASYNCIFY_IMPORTS``.
219+
220+
.. code-block:: cpp
221+
222+
// sleep.cpp
223+
#include <emscripten.h>
224+
225+
extern "C" void sleep_for_seconds() {
226+
emscripten_sleep(100);
227+
}
228+
229+
In the side module, you can compile sleep.cpp in the ordinal emscripten dynamic
230+
linking manner:
231+
232+
::
233+
234+
emcc sleep.cpp -O3 -o libsleep.wasm -sASYNCIFY -sSIDE_MODULE
235+
236+
.. code-block:: cpp
237+
238+
// main.cpp
239+
#include <emscripten.h>
240+
241+
extern "C" void sleep_for_seconds();
242+
243+
int main() {
244+
sleep_for_seconds();
245+
return 0;
246+
}
247+
248+
In the main module, the compiler doesn’t statically know that ``sleep_for_seconds`` is
249+
asynchronous. Therefore, you must add ``sleep_for_seconds`` to the ``ASYNCIFY_IMPORTS``
250+
list.
251+
252+
::
253+
254+
emcc main.cpp libsleep.wasm -O3 -sASYNCIFY -sASYNCIFY_IMPORTS=sleep_for_seconds -sMAIN_MODULE
255+
213256
Usage with Embind
214257
#################
215258

src/library.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3628,6 +3628,12 @@ mergeInto(LibraryManager.library, {
36283628
__c_longjmp: "new WebAssembly.Tag({'parameters': ['{{{ POINTER_WASM_TYPE }}}']})",
36293629
__c_longjmp_import: true,
36303630
#endif
3631+
#if ASYNCIFY
3632+
__asyncify_state: "new WebAssembly.Global({'value': 'i32', 'mutable': true}, 0)",
3633+
__asyncify_state__import: true,
3634+
__asyncify_data: "new WebAssembly.Global({'value': 'i32', 'mutable': true}, 0)",
3635+
__asyncify_data__import: true,
3636+
#endif
36313637
#endif
36323638

36333639
_emscripten_fs_load_embedded_files__deps: ['$FS', '$PATH'],

src/library_async.js

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -187,6 +187,9 @@ mergeInto(LibraryManager.library, {
187187
}
188188
}
189189
};
190+
#if MAIN_MODULE
191+
ret[x].orig = original;
192+
#endif
190193
} else {
191194
ret[x] = original;
192195
}
@@ -260,10 +263,20 @@ mergeInto(LibraryManager.library, {
260263
{{{ makeSetValue('ptr', C_STRUCTS.asyncify_data_s.rewind_id, 'rewindId', 'i32') }}};
261264
},
262265

266+
#if RELOCATABLE
267+
getDataRewindFunc__deps: [ '$resolveGlobalSymbol' ],
268+
#endif
263269
getDataRewindFunc: function(ptr) {
264270
var id = {{{ makeGetValue('ptr', C_STRUCTS.asyncify_data_s.rewind_id, 'i32') }}};
265271
var name = Asyncify.callStackIdToName[id];
266272
var func = Module['asm'][name];
273+
#if RELOCATABLE
274+
// Exported functions in side modules are not listed in `Module["asm"]`,
275+
// So we should use `resolveGlobalSymbol` helper function, which is defined in `library_dylink.js`.
276+
if (!func) {
277+
func = resolveGlobalSymbol(name, false);
278+
}
279+
#endif
267280
return func;
268281
},
269282

src/library_dylink.js

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -624,6 +624,9 @@ var LibraryDylink = {
624624
// add new entries to functionsInTableMap
625625
updateTableMap(tableBase, metadata.tableSize);
626626
moduleExports = relocateExports(instance.exports, memoryBase);
627+
#if ASYNCIFY
628+
moduleExports = Asyncify.instrumentWasmExports(moduleExports);
629+
#endif
627630
if (!flags.allowUndefined) {
628631
reportUndefinedSymbols();
629632
}
@@ -1020,6 +1023,13 @@ var LibraryDylink = {
10201023
#if DYLINK_DEBUG
10211024
err('dlsym: ' + symbol + ' getting table slot for: ' + result);
10221025
#endif
1026+
1027+
#if ASYNCIFY
1028+
// Asyncify wraps exports, and we need to look through those wrappers.
1029+
if ('orig' in result) {
1030+
result = result.orig;
1031+
}
1032+
#endif
10231033
// Insert the function into the wasm table. If its a direct wasm function
10241034
// the second argument will not be needed. If its a JS function we rely
10251035
// on the `sig` attribute being set based on the `<func>__sig` specified

tests/test_core.py

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3975,6 +3975,40 @@ def test_dlfcn_feature_in_lib(self):
39753975
'''
39763976
self.do_run(src, 'float: 42.\n')
39773977

3978+
@needs_dylink
3979+
@no_wasm64('TODO: asyncify for wasm64')
3980+
def test_dlfcn_asyncify(self):
3981+
self.set_setting('ASYNCIFY')
3982+
3983+
create_file('liblib.c', r'''
3984+
#include <stdio.h>
3985+
#include <emscripten/emscripten.h>
3986+
3987+
int side_module_run() {
3988+
printf("before sleep\n");
3989+
emscripten_sleep(1000);
3990+
printf("after sleep\n");
3991+
return 42;
3992+
}
3993+
''')
3994+
self.build_dlfcn_lib('liblib.c')
3995+
3996+
self.prep_dlfcn_main()
3997+
src = r'''
3998+
#include <stdio.h>
3999+
#include <dlfcn.h>
4000+
4001+
typedef int (*func_t)();
4002+
4003+
int main(int argc, char **argv) {
4004+
void *_dlHandle = dlopen("liblib.so", RTLD_NOW | RTLD_LOCAL);
4005+
func_t my_func = (func_t)dlsym(_dlHandle, "side_module_run");
4006+
printf("%d\n", my_func());
4007+
return 0;
4008+
}
4009+
'''
4010+
self.do_run(src, 'before sleep\nafter sleep\n42\n')
4011+
39784012
def dylink_test(self, main, side, expected=None, header=None, force_c=False,
39794013
main_module=2, **kwargs):
39804014
# Same as dylink_testf but take source code in string form
@@ -8101,6 +8135,36 @@ def test_asyncify_indirect_lists(self, args, should_pass):
81018135
if should_pass:
81028136
raise
81038137

8138+
@needs_dylink
8139+
@no_wasm64('TODO: asyncify for wasm64')
8140+
def test_asyncify_side_module(self):
8141+
self.set_setting('ASYNCIFY')
8142+
self.set_setting('ASYNCIFY_IMPORTS', ['my_sleep'])
8143+
self.dylink_test(r'''
8144+
#include <stdio.h>
8145+
#include "header.h"
8146+
8147+
int main() {
8148+
printf("before sleep\n");
8149+
my_sleep(1);
8150+
printf("after sleep\n");
8151+
return 0;
8152+
}
8153+
''', r'''
8154+
#include <stdio.h>
8155+
#include <emscripten.h>
8156+
#include "header.h"
8157+
8158+
void my_sleep(int milli_seconds) {
8159+
// put variable onto stack
8160+
volatile int value = 42;
8161+
printf("%d\n", value);
8162+
emscripten_sleep(milli_seconds);
8163+
// variable on stack in side module function should be restored.
8164+
printf("%d\n", value);
8165+
}
8166+
''', 'before sleep\n42\n42\nafter sleep\n', header='void my_sleep(int);', force_c=True)
8167+
81048168
@no_asan('asyncify stack operations confuse asan')
81058169
@no_wasm64('TODO: asyncify for wasm64')
81068170
def test_emscripten_scan_registers(self):

0 commit comments

Comments
 (0)