Skip to content

Commit 35e1269

Browse files
authored
Asyncify: Support for new add-list, and updates for binaryen list changes (#11438)
Add support for the new ASYNCIFY_ADD_LIST, and update existing list names following the updates in Binaryen, so that now we have ASYNCIFY_ADD_LIST to add a function, ASYNCIFY_REMOVE_LIST to remove one (previously this was called ASYNCIFY_BLACKLIST), and ASYNCIFY_ONLY_LIST to set a list of the only functions to instrument and no others (previously this was called ASYNCIFY_WHITELIST). The updated lists also handle indirect calls properly, so that if you use ASYNCIFY_IGNORE_INDIRECT and then add (using either the add-list or the only-list) all the functions that are on the stack when pausing, then things will work (for more, see WebAssembly/binaryen#2913). A new test is added to show this.
1 parent 1b58a80 commit 35e1269

11 files changed

+118
-38
lines changed

ChangeLog.md

+10
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,16 @@ See docs/process.md for how version tagging works.
1717

1818
Current Trunk
1919
-------------
20+
- Add support for the new `ASYNCIFY_ADD_LIST`, and update existing list names
21+
following the updates in Binaryen, so that now we have `ASYNCIFY_ADD_LIST` to
22+
add a function, `ASYNCIFY_REMOVE_LIST` to remove one (previously this was
23+
called `ASYNCIFY_BLACKLIST`), and `ASYNCIFY_ONLY_LIST` to set a list of the
24+
only functions to instrument and no others (previously this was called
25+
`ASYNCIFY_WHITELIST`). The updated lists also handle indirect calls properly,
26+
so that if you use `ASYNCIFY_IGNORE_INDIRECT` and then add (using either the
27+
add-list or the only-list) all the functions that are on the stack when
28+
pausing, then things will work (for more, see
29+
https://github.com/WebAssembly/binaryen/pull/2913).
2030

2131
1.39.18: 06/12/2020
2232
-------------------

emcc.py

+11-9
Original file line numberDiff line numberDiff line change
@@ -656,7 +656,6 @@ def backend_binaryen_passes():
656656
# be able to whitelist them etc.
657657
passes += ['--fpcast-emu']
658658
if shared.Settings.ASYNCIFY:
659-
# TODO: allow whitelist as in asyncify
660659
passes += ['--asyncify']
661660
if shared.Settings.ASSERTIONS:
662661
passes += ['--pass-arg=asyncify-asserts']
@@ -694,15 +693,18 @@ def check_human_readable_list(items):
694693
logger.warning('''emcc: ASYNCIFY list contains an item without balanced parentheses ("(", ")"):''')
695694
logger.warning(''' ''' + item)
696695
logger.warning('''This may indicate improper escaping that led to splitting inside your names.''')
697-
logger.warning('''Try to quote the entire argument, like this: -s 'ASYNCIFY_WHITELIST=["foo(int, char)", "bar"]' ''')
696+
logger.warning('''Try to quote the entire argument, like this: -s 'ASYNCIFY_ONLY_LIST=["foo(int, char)", "bar"]' ''')
698697
break
699698

700-
if shared.Settings.ASYNCIFY_BLACKLIST:
701-
check_human_readable_list(shared.Settings.ASYNCIFY_BLACKLIST)
702-
passes += ['--pass-arg=asyncify-blacklist@%s' % ','.join(shared.Settings.ASYNCIFY_BLACKLIST)]
703-
if shared.Settings.ASYNCIFY_WHITELIST:
704-
check_human_readable_list(shared.Settings.ASYNCIFY_WHITELIST)
705-
passes += ['--pass-arg=asyncify-whitelist@%s' % ','.join(shared.Settings.ASYNCIFY_WHITELIST)]
699+
if shared.Settings.ASYNCIFY_REMOVE_LIST:
700+
check_human_readable_list(shared.Settings.ASYNCIFY_REMOVE_LIST)
701+
passes += ['--pass-arg=asyncify-removelist@%s' % ','.join(shared.Settings.ASYNCIFY_REMOVE_LIST)]
702+
if shared.Settings.ASYNCIFY_ADD_LIST:
703+
check_human_readable_list(shared.Settings.ASYNCIFY_ADD_LIST)
704+
passes += ['--pass-arg=asyncify-addlist@%s' % ','.join(shared.Settings.ASYNCIFY_ADD_LIST)]
705+
if shared.Settings.ASYNCIFY_ONLY_LIST:
706+
check_human_readable_list(shared.Settings.ASYNCIFY_ONLY_LIST)
707+
passes += ['--pass-arg=asyncify-onlylist@%s' % ','.join(shared.Settings.ASYNCIFY_ONLY_LIST)]
706708
if shared.Settings.BINARYEN_IGNORE_IMPLICIT_TRAPS:
707709
passes += ['--ignore-implicit-traps']
708710

@@ -3177,7 +3179,7 @@ def do_binaryen(target, asm_target, options, memfile, wasm_binary_target,
31773179
debug_info = shared.Settings.DEBUG_LEVEL >= 2 or options.profiling_funcs
31783180
# whether we need to emit -g in the intermediate binaryen invocations (but not necessarily at the very end).
31793181
# this is necessary for emitting a symbol map at the end.
3180-
intermediate_debug_info = bool(debug_info or options.emit_symbol_map or shared.Settings.ASYNCIFY_WHITELIST or shared.Settings.ASYNCIFY_BLACKLIST)
3182+
intermediate_debug_info = bool(debug_info or options.emit_symbol_map or shared.Settings.ASYNCIFY_ONLY_LIST or shared.Settings.ASYNCIFY_REMOVE_LIST or shared.Settings.ASYNCIFY_ADD_LIST)
31813183
emit_symbol_map = options.emit_symbol_map or shared.Settings.CYBERDWARF
31823184
# finish compiling to WebAssembly, using asm2wasm, if we didn't already emit WebAssembly directly using the wasm backend.
31833185
if not shared.Settings.WASM_BACKEND:

emscripten.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -2092,7 +2092,7 @@ def finalize_wasm(temp_files, infile, outfile, memfile, DEBUG):
20922092

20932093
# tell binaryen to look at the features section, and if there isn't one, to use MVP
20942094
# (which matches what llvm+lld has given us)
2095-
if shared.Settings.DEBUG_LEVEL >= 2 or shared.Settings.PROFILING_FUNCS or shared.Settings.EMIT_SYMBOL_MAP or shared.Settings.ASYNCIFY_WHITELIST or shared.Settings.ASYNCIFY_BLACKLIST:
2095+
if shared.Settings.DEBUG_LEVEL >= 2 or shared.Settings.PROFILING_FUNCS or shared.Settings.EMIT_SYMBOL_MAP or shared.Settings.ASYNCIFY_ONLY_LIST or shared.Settings.ASYNCIFY_REMOVE_LIST or shared.Settings.ASYNCIFY_ADD_LIST:
20962096
args.append('-g')
20972097
if shared.Settings.WASM_BIGINT:
20982098
args.append('--bigint')

site/source/docs/porting/asyncify.rst

+9-5
Original file line numberDiff line numberDiff line change
@@ -243,11 +243,15 @@ you can tell Asyncify to ignore indirect calls using
243243
If you know that some indirect calls matter and others do not, then you
244244
can provide a manual list of functions to Asyncify:
245245

246-
* ``ASYNCIFY_BLACKLIST`` is a list of functions that do not unwind the stack.
247-
Asyncify will do it's normal whole-program analysis under the assumption
248-
that those do not unwind.
249-
* ``ASYNCIFY_WHITELIST`` is a list of the **only** functions that can unwind
250-
the stack. Asyncify will instrument those and no others.
246+
* ``ASYNCIFY_REMOVE_LIST`` is a list of functions that do not unwind the stack.
247+
Asyncify will do its normal whole-program analysis, then remove these
248+
functions from the list of instrumented functions.
249+
* ``ASYNCIFY_ADD_LIST`` is a list of functions that do unwind the stack, and
250+
are added after doing the normal whole-program analysis. This is mostly useful
251+
if you use ``ASYNCIFY_IGNORE_INDIRECT`` but want to also mark some additional
252+
functions that need to unwind.
253+
* ``ASYNCIFY_ONLY_LIST`` is a list of the **only** functions that can unwind
254+
the stack. Asyncify will instrument exactly those and no others.
251255

252256
For more details see ``settings.js``. Note that the manual settings
253257
mentioned here are error-prone - if you don't get things exactly right,

src/settings.js

+19-8
Original file line numberDiff line numberDiff line change
@@ -691,7 +691,7 @@ var NODEJS_CATCH_REJECTION = 1;
691691
// This allows to inject some async functions to the C code that appear to be sync
692692
// e.g. emscripten_sleep
693693
// On fastcomp this uses the Asyncify IR transform.
694-
// On upstream this uses the Asyncify pass in Binaryen. TODO: whitelist, coroutines
694+
// On upstream this uses the Asyncify pass in Binaryen.
695695
var ASYNCIFY = 0;
696696

697697
// Imports which can do a sync operation, in addition to the default ones that
@@ -714,7 +714,7 @@ var ASYNCIFY_IGNORE_INDIRECT = 0;
714714
// In that case, you should increase this size.
715715
var ASYNCIFY_STACK_SIZE = 4096;
716716

717-
// If the Asyncify blacklist is provided, then the functions in it will not
717+
// If the Asyncify remove-list is provided, then the functions in it will not
718718
// be instrumented even if it looks like they need to. This can be useful
719719
// if you know things the whole-program analysis doesn't, like if you
720720
// know certain indirect calls are safe and won't unwind. But if you
@@ -735,13 +735,22 @@ var ASYNCIFY_STACK_SIZE = 4096;
735735
// builds, etc.). You can inspect the wasm binary to look for the actual names,
736736
// either directly or using wasm-objdump or wasm-dis, etc.
737737
// Simple '*' wildcard matching is supported.
738-
var ASYNCIFY_BLACKLIST = [];
739-
740-
// If the Asyncify whitelist is provided, then *only* the functions in the list
741-
// will be instrumented. Like the blacklist, getting this wrong will break
738+
var ASYNCIFY_REMOVE_LIST = [];
739+
740+
// Functions in the Asyncify add-list are added to the list of instrumented
741+
// functions, that is, they will be instrumented even if otherwise asyncify
742+
// thinks they don't need to be. As by default everything will be instrumented
743+
// in the safest way possible, this is only useful if you use IGNORE_INDIRECT
744+
// and use this list to fix up some indirect calls that *do* need to be
745+
// instrumented.
746+
// See notes on ASYNCIFY_REMOVE_LIST about the names.
747+
var ASYNCIFY_ADD_LIST = [];
748+
749+
// If the Asyncify only-list is provided, then *only* the functions in the list
750+
// will be instrumented. Like the remove-list, getting this wrong will break
742751
// your application.
743-
// See notes on ASYNCIFY_BLACKLIST about the names.
744-
var ASYNCIFY_WHITELIST = [];
752+
// See notes on ASYNCIFY_REMOVE_LIST about the names.
753+
var ASYNCIFY_ONLY_LIST = [];
745754

746755
// Allows lazy code loading: where emscripten_lazy_load_code() is written, we
747756
// will pause execution, load the rest of the code, and then resume.
@@ -1817,4 +1826,6 @@ var LEGACY_SETTINGS = [
18171826
['BINARYEN_MEM_MAX', 'MAXIMUM_MEMORY'],
18181827
['BINARYEN_PASSES', [''], 'Use BINARYEN_EXTRA_PASSES to add additional passes'],
18191828
['SWAPPABLE_ASM_MODULE', [0], 'Fully swappable asm modules are no longer supported'],
1829+
['ASYNCIFY_WHITELIST', 'ASYNCIFY_ONLY_LIST'],
1830+
['ASYNCIFY_BLACKLIST', 'ASYNCIFY_REMOVE_LIST'],
18201831
];
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
#include <assert.h>
2+
#include <stdio.h>
3+
#include <emscripten.h>
4+
5+
int x = 0;
6+
7+
struct Structy {
8+
virtual int virty() {
9+
if (x == 1337) return virty(); // don't inline me
10+
emscripten_sleep(1);
11+
return 42;
12+
}
13+
};
14+
15+
Structy y;
16+
17+
__attribute__((noinline))
18+
void virt() {
19+
if (x == 1337) {
20+
// don't inline me
21+
virt();
22+
}
23+
Structy *z = &y;
24+
printf("virt: %d\n", z->virty()); // but the indirect call itself!
25+
}
26+
27+
int main() {
28+
EM_ASM({
29+
Module.counter = (Module.counter || 0) + 1;
30+
if (Module.counter > 10) throw "infinite loop due to asyncify bug?";
31+
});
32+
virt();
33+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
virt: 42

tests/test_browser.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -3307,8 +3307,8 @@ def test_async_stack_overflow(self):
33073307
self.btest('browser/async_stack_overflow.cpp', '0', args=['-s', 'ASYNCIFY', '-s', 'ASYNCIFY_STACK_SIZE=4'])
33083308

33093309
@no_fastcomp('wasm backend asyncify specific')
3310-
def test_async_bad_whitelist(self):
3311-
self.btest('browser/async_bad_whitelist.cpp', '0', args=['-s', 'ASYNCIFY', '-s', 'ASYNCIFY_WHITELIST=["waka"]', '--profiling'])
3310+
def test_async_bad_list(self):
3311+
self.btest('browser/async_bad_list.cpp', '0', args=['-s', 'ASYNCIFY', '-s', 'ASYNCIFY_ONLY_LIST=["waka"]', '--profiling'])
33123312

33133313
# Tests that when building with -s MINIMAL_RUNTIME=1, the build can use -s MODULARIZE=1 as well.
33143314
def test_minimal_runtime_modularize(self):

tests/test_core.py

+29-10
Original file line numberDiff line numberDiff line change
@@ -7862,22 +7862,22 @@ def test_asyncify_unused(self):
78627862

78637863
@parameterized({
78647864
'normal': ([], True),
7865-
'blacklist_a': (['-s', 'ASYNCIFY_BLACKLIST=["foo(int, double)"]'], False),
7866-
'blacklist_b': (['-s', 'ASYNCIFY_BLACKLIST=["bar()"]'], True),
7867-
'blacklist_c': (['-s', 'ASYNCIFY_BLACKLIST=["baz()"]'], False),
7868-
'whitelist_a': (['-s', 'ASYNCIFY_WHITELIST=["main","__original_main","foo(int, double)","baz()","c_baz","Structy::funcy()","bar()"]'], True),
7869-
'whitelist_b': (['-s', 'ASYNCIFY_WHITELIST=["main","__original_main","foo(int, double)","baz()","c_baz","Structy::funcy()"]'], True),
7870-
'whitelist_c': (['-s', 'ASYNCIFY_WHITELIST=["main","__original_main","foo(int, double)","baz()","c_baz"]'], False),
7871-
'whitelist_d': (['-s', 'ASYNCIFY_WHITELIST=["foo(int, double)","baz()","c_baz","Structy::funcy()"]'], False),
7872-
'whitelist_b_response': ([], True, '["main","__original_main","foo(int, double)","baz()","c_baz","Structy::funcy()"]'),
7873-
'whitelist_c_response': ([], False, '["main","__original_main","foo(int, double)","baz()","c_baz"]'),
7865+
'removelist_a': (['-s', 'ASYNCIFY_REMOVE_LIST=["foo(int, double)"]'], False),
7866+
'removelist_b': (['-s', 'ASYNCIFY_REMOVE_LIST=["bar()"]'], True),
7867+
'removelist_c': (['-s', 'ASYNCIFY_REMOVE_LIST=["baz()"]'], False),
7868+
'onlylist_a': (['-s', 'ASYNCIFY_ONLY_LIST=["main","__original_main","foo(int, double)","baz()","c_baz","Structy::funcy()","bar()"]'], True),
7869+
'onlylist_b': (['-s', 'ASYNCIFY_ONLY_LIST=["main","__original_main","foo(int, double)","baz()","c_baz","Structy::funcy()"]'], True),
7870+
'onlylist_c': (['-s', 'ASYNCIFY_ONLY_LIST=["main","__original_main","foo(int, double)","baz()","c_baz"]'], False),
7871+
'onlylist_d': (['-s', 'ASYNCIFY_ONLY_LIST=["foo(int, double)","baz()","c_baz","Structy::funcy()"]'], False),
7872+
'onlylist_b_response': ([], True, '["main","__original_main","foo(int, double)","baz()","c_baz","Structy::funcy()"]'),
7873+
'onlylist_c_response': ([], False, '["main","__original_main","foo(int, double)","baz()","c_baz"]'),
78747874
})
78757875
@no_asan('asan is not compatible with asyncify stack operations; may also need to not instrument asan_c_load_4, TODO')
78767876
@no_fastcomp('new asyncify only')
78777877
def test_asyncify_lists(self, args, should_pass, response=None):
78787878
if response is not None:
78797879
create_test_file('response.file', response)
7880-
self.emcc_args += ['-s', 'ASYNCIFY_WHITELIST[email protected]']
7880+
self.emcc_args += ['-s', 'ASYNCIFY_ONLY_LIST[email protected]']
78817881
self.set_setting('ASYNCIFY', 1)
78827882
self.emcc_args += args
78837883
try:
@@ -7889,6 +7889,25 @@ def test_asyncify_lists(self, args, should_pass, response=None):
78897889
if should_pass:
78907890
raise
78917891

7892+
@parameterized({
7893+
'normal': ([], True),
7894+
'ignoreindirect': (['-s', 'ASYNCIFY_IGNORE_INDIRECT'], False),
7895+
'add': (['-s', 'ASYNCIFY_IGNORE_INDIRECT', '-s', 'ASYNCIFY_ADD_LIST=["main","virt()"]'], True),
7896+
})
7897+
@no_asan('asan is not compatible with asyncify stack operations; may also need to not instrument asan_c_load_4, TODO')
7898+
@no_fastcomp('new asyncify only')
7899+
def test_asyncify_indirect_lists(self, args, should_pass):
7900+
self.set_setting('ASYNCIFY', 1)
7901+
self.emcc_args += args
7902+
try:
7903+
self.do_run_in_out_file_test('tests', 'core', 'test_asyncify_indirect_lists', assert_identical=True)
7904+
if not should_pass:
7905+
should_pass = True
7906+
raise Exception('should not have passed')
7907+
except Exception:
7908+
if should_pass:
7909+
raise
7910+
78927911
@no_asan('asyncify stack operations confuse asan')
78937912
@no_fastcomp('wasm-backend specific feature')
78947913
def test_emscripten_scan_registers(self):

tests/test_other.py

+3-3
Original file line numberDiff line numberDiff line change
@@ -8931,7 +8931,7 @@ def test_emcc_parsing(self):
89318931

89328932
@no_fastcomp('uses new ASYNCIFY')
89338933
def test_asyncify_escaping(self):
8934-
proc = run_process([EMCC, path_from_root('tests', 'hello_world.c'), '-s', 'ASYNCIFY=1', '-s', "ASYNCIFY_WHITELIST=[DOS_ReadFile(unsigned short, unsigned char*, unsigned short*, bool)]"], stdout=PIPE, stderr=PIPE)
8934+
proc = run_process([EMCC, path_from_root('tests', 'hello_world.c'), '-s', 'ASYNCIFY=1', '-s', "ASYNCIFY_ONLY_LIST=[DOS_ReadFile(unsigned short, unsigned char*, unsigned short*, bool)]"], stdout=PIPE, stderr=PIPE)
89358935
self.assertContained('emcc: ASYNCIFY list contains an item without balanced parentheses', proc.stderr)
89368936
self.assertContained(' DOS_ReadFile(unsigned short', proc.stderr)
89378937
self.assertContained('Try to quote the entire argument', proc.stderr)
@@ -8943,10 +8943,10 @@ def test_asyncify_response_file(self):
89438943
"DOS_ReadFile(unsigned short, unsigned char*, unsigned short*, bool)"
89448944
]
89458945
''')
8946-
proc = run_process([EMCC, path_from_root('tests', 'hello_world.c'), '-s', 'ASYNCIFY=1', '-s', "ASYNCIFY_WHITELIST[email protected]"], stdout=PIPE, stderr=PIPE)
8946+
proc = run_process([EMCC, path_from_root('tests', 'hello_world.c'), '-s', 'ASYNCIFY=1', '-s', "ASYNCIFY_ONLY_LIST[email protected]"], stdout=PIPE, stderr=PIPE)
89478947
# we should parse the response file properly, and then issue a proper warning for the missing function
89488948
self.assertContained(
8949-
'Asyncify whitelist contained a non-matching pattern: DOS_ReadFile(unsigned short, unsigned char*, unsigned short*, bool)',
8949+
'Asyncify onlylist contained a non-matching pattern: DOS_ReadFile(unsigned short, unsigned char*, unsigned short*, bool)',
89508950
proc.stderr)
89518951

89528952
# Sockets and networking

0 commit comments

Comments
 (0)