Skip to content

Commit c3a9886

Browse files
authored
Fix emterpreter SAFE_HEAP invokes. fixes #6532 (#6539)
We must not emterpret SAFE_FT_MASK, as it appears in dynCall_*, which may be called during rebuilding of the stack after an async operation (if an invoke was on the stack, then we will call it again, and it calls dynCall_*), and it is not valid to run emterpreted code at that time.
1 parent 345d9aa commit c3a9886

File tree

3 files changed

+88
-3
lines changed

3 files changed

+88
-3
lines changed

tests/runner.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -140,13 +140,14 @@ def get_browser():
140140
]
141141
nondefault_test_modes = [
142142
'asm2f',
143-
'asm2i',
144143
'binaryen0',
145144
'binaryen1',
146145
'binaryen2',
147146
'binaryen3',
148147
'binaryens',
149148
'binaryenz',
149+
'asmi',
150+
'asm2i',
150151
]
151152
test_index = 0
152153

tests/test_core.py

Lines changed: 75 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7407,6 +7407,77 @@ def test_async_abort(self):
74077407
self.emcc_args += ['--js-library', 'lib.js']
74087408
self.do_run(src, 'Hello')
74097409

7410+
def test_async_invoke_safe_heap(self):
7411+
if not self.is_emterpreter(): return self.skip('emterpreter-only test')
7412+
7413+
self.banned_js_engines = [SPIDERMONKEY_ENGINE, V8_ENGINE] # needs setTimeout which only node has
7414+
7415+
# SAFE_HEAP leads to SAFE_FT_MASK, which appear in dynCall_*
7416+
# and then if they are interpreted, that messes up reloading
7417+
# of the stack (we can't run emterpreted code at that time,
7418+
# we should just see calls and follow them).
7419+
Settings.EMTERPRETIFY_ASYNC = 1
7420+
Settings.SAFE_HEAP = 1
7421+
Settings.EXPORTED_FUNCTIONS = ['_async_callback_test']
7422+
Settings.EXTRA_EXPORTED_RUNTIME_METHODS = ["ccall"]
7423+
Settings.DISABLE_EXCEPTION_CATCHING = 0
7424+
Settings.ALLOW_MEMORY_GROWTH = 1
7425+
Settings.EMTERPRETIFY = 1
7426+
Settings.EMTERPRETIFY_ASYNC = 1
7427+
Settings.ASSERTIONS = 2
7428+
7429+
open('post.js', 'w').write(r'''
7430+
var AsyncOperation = {
7431+
done: false,
7432+
7433+
start: function() {
7434+
// this.done = true; // uncomment this line => no crash
7435+
Promise.resolve().then(function() {
7436+
console.log('done!');
7437+
AsyncOperation.done = true;
7438+
});
7439+
}
7440+
};
7441+
7442+
Module.ccall('async_callback_test', null, [], [], { async: true });
7443+
''')
7444+
7445+
src = r'''
7446+
#include <stdio.h>
7447+
#include <emscripten.h>
7448+
7449+
extern "C" {
7450+
void call_async_operation() {
7451+
printf("start\n");
7452+
EM_ASM({AsyncOperation.start()});
7453+
printf("mid\n");
7454+
while (!EM_ASM_INT({return AsyncOperation.done})) {
7455+
printf("sleep1\n");
7456+
emscripten_sleep(200);
7457+
printf("sleep2\n");
7458+
}
7459+
}
7460+
7461+
// remove throw() => no crash
7462+
static void nothrow_func() throw()
7463+
{
7464+
call_async_operation();
7465+
printf("async operation OK\n");
7466+
}
7467+
7468+
void async_callback_test() {
7469+
nothrow_func();
7470+
}
7471+
}'''
7472+
7473+
self.emcc_args += [
7474+
'--post-js', 'post.js',
7475+
'--profiling-funcs',
7476+
'--minify', '0',
7477+
'--memory-init-file', '0'
7478+
]
7479+
self.do_run(src, 'async operation OK')
7480+
74107481
def do_test_coroutine(self, additional_settings):
74117482
Settings.NO_EXIT_RUNTIME = 0 # needs to flush stdio streams
74127483
src = open(path_from_root('tests', 'test_coroutines.cpp')).read()
@@ -7632,7 +7703,6 @@ def setUp(self):
76327703

76337704
# asm.js
76347705
asm2f = make_run('asm2f', compiler=CLANG, emcc_args=['-Oz', '-s', 'PRECISE_F32=1', '-s', 'ALLOW_MEMORY_GROWTH=1', '-s', 'WASM=0'])
7635-
asm2i = make_run('asm2i', compiler=CLANG, emcc_args=['-O2', '-s', 'EMTERPRETIFY=1', '-s', 'WASM=0'])
76367706
asm2nn = make_run('asm2nn', compiler=CLANG, emcc_args=['-O2', '-s', 'WASM=0'], env={'EMCC_NATIVE_OPTIMIZER': '0'})
76377707

76387708
# wasm
@@ -7641,4 +7711,8 @@ def setUp(self):
76417711
binaryen2s = make_run('binaryen2s', compiler=CLANG, emcc_args=['-O2', '-s', 'SAFE_HEAP=1'])
76427712
binaryen2_interpret = make_run('binaryen2_interpret', compiler=CLANG, emcc_args=['-O2', '-s', 'BINARYEN_METHOD="interpret-binary"'])
76437713

7714+
# emterpreter
7715+
asmi = make_run('asmi', compiler=CLANG, emcc_args=['-s', 'EMTERPRETIFY=1', '-s', 'WASM=0'])
7716+
asm2i = make_run('asm2i', compiler=CLANG, emcc_args=['-O2', '-s', 'EMTERPRETIFY=1', '-s', 'WASM=0'])
7717+
76447718
del T # T is just a shape for the specific subclasses, we don't test it itself

tools/emterpretify.py

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,17 @@ def handle_arg(arg):
6161

6262
# consts
6363

64-
BLACKLIST = set(['_malloc', '_free', '_memcpy', '_memmove', '_memset', '_strlen', 'stackAlloc', 'setThrew', 'stackRestore', 'setTempRet0', 'getTempRet0', 'stackSave', '_emscripten_autodebug_double', '_emscripten_autodebug_float', '_emscripten_autodebug_i8', '_emscripten_autodebug_i16', '_emscripten_autodebug_i32', '_emscripten_autodebug_i64', '_strncpy', '_strcpy', '_strcat', '_saveSetjmp', '_testSetjmp', '_emscripten_replace_memory', '_bitshift64Shl', '_bitshift64Ashr', '_bitshift64Lshr', 'setAsyncState', 'emtStackSave', 'emtStackRestore', 'getEmtStackMax', 'setEmtStackMax'])
64+
# The blacklist contains functions we will not emterpret in any case: they are known to be safe to run normally, e.g.
65+
# because they don't call anything, or they only call trivial things we know are safe.
66+
# One particularly interesting case is SAFE_FT_MASK: we must not emterpret it, as it appears in expressions like
67+
# FUNCTION_TABLE_vi[SAFE_FT_MASK(..) & 7](..)
68+
# which means that if we are in async mode and reloading the stack to resume, and we need to make that call as
69+
# part of getting there - say, if an invoke was part of the path to get here, and invoke calls dynCall which
70+
# calls SAFE_FT_MASK - then we'll end up doing that call during recreating the stack, which breaks. In other
71+
# words, dynCall_* must be calls without running emterpreted code in them. To avoid that, we blacklist
72+
# SAFE_FT_MASK, which should be blacklisted anyhow as it has no need for emterpretation.
73+
BLACKLIST = set(['_malloc', '_free', '_memcpy', '_memmove', '_memset', '_strlen', 'stackAlloc', 'setThrew', 'stackRestore', 'setTempRet0', 'getTempRet0', 'stackSave', '_emscripten_autodebug_double', '_emscripten_autodebug_float', '_emscripten_autodebug_i8', '_emscripten_autodebug_i16', '_emscripten_autodebug_i32', '_emscripten_autodebug_i64', '_strncpy', '_strcpy', '_strcat', '_saveSetjmp', '_testSetjmp', '_emscripten_replace_memory', '_bitshift64Shl', '_bitshift64Ashr', '_bitshift64Lshr', 'setAsyncState', 'emtStackSave', 'emtStackRestore', 'getEmtStackMax', 'setEmtStackMax', 'SAFE_FT_MASK', 'SAFE_HEAP_LOAD', 'SAFE_HEAP_LOAD_D', 'SAFE_HEAP_STORE', 'SAFE_HEAP_STORE_D'])
74+
6575
WHITELIST = []
6676

6777
SYNC_FUNCS = set(['_emscripten_sleep', '_emscripten_sleep_with_yield', '_emscripten_wget_data', '_emscripten_idb_load', '_emscripten_idb_store', '_emscripten_idb_delete', '_emscripten_yield'])

0 commit comments

Comments
 (0)