Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Asyncify: Support for new add-list, and updates for binaryen list changes #11438

Merged
merged 9 commits into from
Jun 18, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions ChangeLog.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,16 @@ See docs/process.md for how version tagging works.

Current Trunk
-------------
- 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
https://github.com/WebAssembly/binaryen/pull/2913).

1.39.18: 06/12/2020
-------------------
Expand Down
20 changes: 11 additions & 9 deletions emcc.py
Original file line number Diff line number Diff line change
Expand Up @@ -656,7 +656,6 @@ def backend_binaryen_passes():
# be able to whitelist them etc.
passes += ['--fpcast-emu']
if shared.Settings.ASYNCIFY:
# TODO: allow whitelist as in asyncify
passes += ['--asyncify']
if shared.Settings.ASSERTIONS:
passes += ['--pass-arg=asyncify-asserts']
Expand Down Expand Up @@ -694,15 +693,18 @@ def check_human_readable_list(items):
logger.warning('''emcc: ASYNCIFY list contains an item without balanced parentheses ("(", ")"):''')
logger.warning(''' ''' + item)
logger.warning('''This may indicate improper escaping that led to splitting inside your names.''')
logger.warning('''Try to quote the entire argument, like this: -s 'ASYNCIFY_WHITELIST=["foo(int, char)", "bar"]' ''')
logger.warning('''Try to quote the entire argument, like this: -s 'ASYNCIFY_ONLY_LIST=["foo(int, char)", "bar"]' ''')
break

if shared.Settings.ASYNCIFY_BLACKLIST:
check_human_readable_list(shared.Settings.ASYNCIFY_BLACKLIST)
passes += ['--pass-arg=asyncify-blacklist@%s' % ','.join(shared.Settings.ASYNCIFY_BLACKLIST)]
if shared.Settings.ASYNCIFY_WHITELIST:
check_human_readable_list(shared.Settings.ASYNCIFY_WHITELIST)
passes += ['--pass-arg=asyncify-whitelist@%s' % ','.join(shared.Settings.ASYNCIFY_WHITELIST)]
if shared.Settings.ASYNCIFY_REMOVE_LIST:
check_human_readable_list(shared.Settings.ASYNCIFY_REMOVE_LIST)
passes += ['--pass-arg=asyncify-removelist@%s' % ','.join(shared.Settings.ASYNCIFY_REMOVE_LIST)]
if shared.Settings.ASYNCIFY_ADD_LIST:
check_human_readable_list(shared.Settings.ASYNCIFY_ADD_LIST)
passes += ['--pass-arg=asyncify-addlist@%s' % ','.join(shared.Settings.ASYNCIFY_ADD_LIST)]
if shared.Settings.ASYNCIFY_ONLY_LIST:
check_human_readable_list(shared.Settings.ASYNCIFY_ONLY_LIST)
passes += ['--pass-arg=asyncify-onlylist@%s' % ','.join(shared.Settings.ASYNCIFY_ONLY_LIST)]
if shared.Settings.BINARYEN_IGNORE_IMPLICIT_TRAPS:
passes += ['--ignore-implicit-traps']

Expand Down Expand Up @@ -3177,7 +3179,7 @@ def do_binaryen(target, asm_target, options, memfile, wasm_binary_target,
debug_info = shared.Settings.DEBUG_LEVEL >= 2 or options.profiling_funcs
# whether we need to emit -g in the intermediate binaryen invocations (but not necessarily at the very end).
# this is necessary for emitting a symbol map at the end.
intermediate_debug_info = bool(debug_info or options.emit_symbol_map or shared.Settings.ASYNCIFY_WHITELIST or shared.Settings.ASYNCIFY_BLACKLIST)
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)
emit_symbol_map = options.emit_symbol_map or shared.Settings.CYBERDWARF
# finish compiling to WebAssembly, using asm2wasm, if we didn't already emit WebAssembly directly using the wasm backend.
if not shared.Settings.WASM_BACKEND:
Expand Down
2 changes: 1 addition & 1 deletion emscripten.py
Original file line number Diff line number Diff line change
Expand Up @@ -2092,7 +2092,7 @@ def finalize_wasm(temp_files, infile, outfile, memfile, DEBUG):

# tell binaryen to look at the features section, and if there isn't one, to use MVP
# (which matches what llvm+lld has given us)
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:
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:
args.append('-g')
if shared.Settings.WASM_BIGINT:
args.append('--bigint')
Expand Down
14 changes: 9 additions & 5 deletions site/source/docs/porting/asyncify.rst
Original file line number Diff line number Diff line change
Expand Up @@ -243,11 +243,15 @@ you can tell Asyncify to ignore indirect calls using
If you know that some indirect calls matter and others do not, then you
can provide a manual list of functions to Asyncify:

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

For more details see ``settings.js``. Note that the manual settings
mentioned here are error-prone - if you don't get things exactly right,
Expand Down
27 changes: 19 additions & 8 deletions src/settings.js
Original file line number Diff line number Diff line change
Expand Up @@ -691,7 +691,7 @@ var NODEJS_CATCH_REJECTION = 1;
// This allows to inject some async functions to the C code that appear to be sync
// e.g. emscripten_sleep
// On fastcomp this uses the Asyncify IR transform.
// On upstream this uses the Asyncify pass in Binaryen. TODO: whitelist, coroutines
// On upstream this uses the Asyncify pass in Binaryen.
var ASYNCIFY = 0;

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

// If the Asyncify blacklist is provided, then the functions in it will not
// If the Asyncify remove-list is provided, then the functions in it will not
// be instrumented even if it looks like they need to. This can be useful
// if you know things the whole-program analysis doesn't, like if you
// know certain indirect calls are safe and won't unwind. But if you
Expand All @@ -735,13 +735,22 @@ var ASYNCIFY_STACK_SIZE = 4096;
// builds, etc.). You can inspect the wasm binary to look for the actual names,
// either directly or using wasm-objdump or wasm-dis, etc.
// Simple '*' wildcard matching is supported.
var ASYNCIFY_BLACKLIST = [];

// If the Asyncify whitelist is provided, then *only* the functions in the list
// will be instrumented. Like the blacklist, getting this wrong will break
var ASYNCIFY_REMOVE_LIST = [];

// Functions in the Asyncify add-list are added to the list of instrumented
// functions, that is, they will be instrumented even if otherwise asyncify
// thinks they don't need to be. As by default everything will be instrumented
// in the safest way possible, this is only useful if you use IGNORE_INDIRECT
// and use this list to fix up some indirect calls that *do* need to be
// instrumented.
// See notes on ASYNCIFY_REMOVE_LIST about the names.
var ASYNCIFY_ADD_LIST = [];

// If the Asyncify only-list is provided, then *only* the functions in the list
// will be instrumented. Like the remove-list, getting this wrong will break
// your application.
// See notes on ASYNCIFY_BLACKLIST about the names.
var ASYNCIFY_WHITELIST = [];
// See notes on ASYNCIFY_REMOVE_LIST about the names.
var ASYNCIFY_ONLY_LIST = [];

// Allows lazy code loading: where emscripten_lazy_load_code() is written, we
// will pause execution, load the rest of the code, and then resume.
Expand Down Expand Up @@ -1817,4 +1826,6 @@ var LEGACY_SETTINGS = [
['BINARYEN_MEM_MAX', 'MAXIMUM_MEMORY'],
['BINARYEN_PASSES', [''], 'Use BINARYEN_EXTRA_PASSES to add additional passes'],
['SWAPPABLE_ASM_MODULE', [0], 'Fully swappable asm modules are no longer supported'],
['ASYNCIFY_WHITELIST', 'ASYNCIFY_ONLY_LIST'],
['ASYNCIFY_BLACKLIST', 'ASYNCIFY_REMOVE_LIST'],
];
File renamed without changes.
33 changes: 33 additions & 0 deletions tests/core/test_asyncify_indirect_lists.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
#include <assert.h>
#include <stdio.h>
#include <emscripten.h>

int x = 0;

struct Structy {
virtual int virty() {
Comment on lines +7 to +8
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like these names :D

if (x == 1337) return virty(); // don't inline me
emscripten_sleep(1);
return 42;
}
};

Structy y;

__attribute__((noinline))
void virt() {
if (x == 1337) {
// don't inline me
virt();
}
Structy *z = &y;
printf("virt: %d\n", z->virty()); // but the indirect call itself!
}

int main() {
EM_ASM({
Module.counter = (Module.counter || 0) + 1;
if (Module.counter > 10) throw "infinite loop due to asyncify bug?";
});
virt();
}
1 change: 1 addition & 0 deletions tests/core/test_asyncify_indirect_lists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
virt: 42
4 changes: 2 additions & 2 deletions tests/test_browser.py
Original file line number Diff line number Diff line change
Expand Up @@ -3307,8 +3307,8 @@ def test_async_stack_overflow(self):
self.btest('browser/async_stack_overflow.cpp', '0', args=['-s', 'ASYNCIFY', '-s', 'ASYNCIFY_STACK_SIZE=4'])

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

# Tests that when building with -s MINIMAL_RUNTIME=1, the build can use -s MODULARIZE=1 as well.
def test_minimal_runtime_modularize(self):
Expand Down
39 changes: 29 additions & 10 deletions tests/test_core.py
Original file line number Diff line number Diff line change
Expand Up @@ -7862,22 +7862,22 @@ def test_asyncify_unused(self):

@parameterized({
'normal': ([], True),
'blacklist_a': (['-s', 'ASYNCIFY_BLACKLIST=["foo(int, double)"]'], False),
'blacklist_b': (['-s', 'ASYNCIFY_BLACKLIST=["bar()"]'], True),
'blacklist_c': (['-s', 'ASYNCIFY_BLACKLIST=["baz()"]'], False),
'whitelist_a': (['-s', 'ASYNCIFY_WHITELIST=["main","__original_main","foo(int, double)","baz()","c_baz","Structy::funcy()","bar()"]'], True),
'whitelist_b': (['-s', 'ASYNCIFY_WHITELIST=["main","__original_main","foo(int, double)","baz()","c_baz","Structy::funcy()"]'], True),
'whitelist_c': (['-s', 'ASYNCIFY_WHITELIST=["main","__original_main","foo(int, double)","baz()","c_baz"]'], False),
'whitelist_d': (['-s', 'ASYNCIFY_WHITELIST=["foo(int, double)","baz()","c_baz","Structy::funcy()"]'], False),
'whitelist_b_response': ([], True, '["main","__original_main","foo(int, double)","baz()","c_baz","Structy::funcy()"]'),
'whitelist_c_response': ([], False, '["main","__original_main","foo(int, double)","baz()","c_baz"]'),
'removelist_a': (['-s', 'ASYNCIFY_REMOVE_LIST=["foo(int, double)"]'], False),
'removelist_b': (['-s', 'ASYNCIFY_REMOVE_LIST=["bar()"]'], True),
'removelist_c': (['-s', 'ASYNCIFY_REMOVE_LIST=["baz()"]'], False),
'onlylist_a': (['-s', 'ASYNCIFY_ONLY_LIST=["main","__original_main","foo(int, double)","baz()","c_baz","Structy::funcy()","bar()"]'], True),
'onlylist_b': (['-s', 'ASYNCIFY_ONLY_LIST=["main","__original_main","foo(int, double)","baz()","c_baz","Structy::funcy()"]'], True),
'onlylist_c': (['-s', 'ASYNCIFY_ONLY_LIST=["main","__original_main","foo(int, double)","baz()","c_baz"]'], False),
'onlylist_d': (['-s', 'ASYNCIFY_ONLY_LIST=["foo(int, double)","baz()","c_baz","Structy::funcy()"]'], False),
'onlylist_b_response': ([], True, '["main","__original_main","foo(int, double)","baz()","c_baz","Structy::funcy()"]'),
'onlylist_c_response': ([], False, '["main","__original_main","foo(int, double)","baz()","c_baz"]'),
})
@no_asan('asan is not compatible with asyncify stack operations; may also need to not instrument asan_c_load_4, TODO')
@no_fastcomp('new asyncify only')
def test_asyncify_lists(self, args, should_pass, response=None):
if response is not None:
create_test_file('response.file', response)
self.emcc_args += ['-s', 'ASYNCIFY_WHITELIST[email protected]']
self.emcc_args += ['-s', 'ASYNCIFY_ONLY_LIST[email protected]']
self.set_setting('ASYNCIFY', 1)
self.emcc_args += args
try:
Expand All @@ -7889,6 +7889,25 @@ def test_asyncify_lists(self, args, should_pass, response=None):
if should_pass:
raise

@parameterized({
'normal': ([], True),
'ignoreindirect': (['-s', 'ASYNCIFY_IGNORE_INDIRECT'], False),
'add': (['-s', 'ASYNCIFY_IGNORE_INDIRECT', '-s', 'ASYNCIFY_ADD_LIST=["main","virt()"]'], True),
})
@no_asan('asan is not compatible with asyncify stack operations; may also need to not instrument asan_c_load_4, TODO')
@no_fastcomp('new asyncify only')
def test_asyncify_indirect_lists(self, args, should_pass):
self.set_setting('ASYNCIFY', 1)
self.emcc_args += args
try:
self.do_run_in_out_file_test('tests', 'core', 'test_asyncify_indirect_lists', assert_identical=True)
if not should_pass:
should_pass = True
raise Exception('should not have passed')
except Exception:
if should_pass:
raise

@no_asan('asyncify stack operations confuse asan')
@no_fastcomp('wasm-backend specific feature')
def test_emscripten_scan_registers(self):
Expand Down
6 changes: 3 additions & 3 deletions tests/test_other.py
Original file line number Diff line number Diff line change
Expand Up @@ -8931,7 +8931,7 @@ def test_emcc_parsing(self):

@no_fastcomp('uses new ASYNCIFY')
def test_asyncify_escaping(self):
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)
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)
self.assertContained('emcc: ASYNCIFY list contains an item without balanced parentheses', proc.stderr)
self.assertContained(' DOS_ReadFile(unsigned short', proc.stderr)
self.assertContained('Try to quote the entire argument', proc.stderr)
Expand All @@ -8943,10 +8943,10 @@ def test_asyncify_response_file(self):
"DOS_ReadFile(unsigned short, unsigned char*, unsigned short*, bool)"
]
''')
proc = run_process([EMCC, path_from_root('tests', 'hello_world.c'), '-s', 'ASYNCIFY=1', '-s', "ASYNCIFY_WHITELIST[email protected]"], stdout=PIPE, stderr=PIPE)
proc = run_process([EMCC, path_from_root('tests', 'hello_world.c'), '-s', 'ASYNCIFY=1', '-s', "ASYNCIFY_ONLY_LIST[email protected]"], stdout=PIPE, stderr=PIPE)
# we should parse the response file properly, and then issue a proper warning for the missing function
self.assertContained(
'Asyncify whitelist contained a non-matching pattern: DOS_ReadFile(unsigned short, unsigned char*, unsigned short*, bool)',
'Asyncify onlylist contained a non-matching pattern: DOS_ReadFile(unsigned short, unsigned char*, unsigned short*, bool)',
proc.stderr)

# Sockets and networking
Expand Down