diff --git a/AUTHORS b/AUTHORS index 63f41e6fe680f..1e75174c768ed 100644 --- a/AUTHORS +++ b/AUTHORS @@ -385,4 +385,5 @@ a license to everyone to use it as detailed in LICENSE.) * Florian Stellbrink * Shane Peelar * Alessandro Pignotti +* Zheng Tao Lee (copyright owned by Autodesk, Inc.) * Martina Kraus \ No newline at end of file diff --git a/emscripten.py b/emscripten.py index f38a55dc3bf08..c0be2038bd843 100644 --- a/emscripten.py +++ b/emscripten.py @@ -1164,6 +1164,13 @@ def math_fix(g): return g if not g.startswith('Math_') else g.split('_')[1] +# asm.js function tables have one table in each linked asm.js module, so we +# can't just dynCall into them - ftCall exists for that purpose. In wasm, +# even linked modules share the table, so it's all fine. +def asm_js_emulated_function_pointers(): + return shared.Settings.EMULATED_FUNCTION_POINTERS and not shared.Settings.WASM + + def make_function_tables_impls(function_table_data): function_tables_impls = [] for sig, table in function_table_data.items(): @@ -1171,8 +1178,13 @@ def make_function_tables_impls(function_table_data): arg_coercions = ' '.join(['a' + str(i) + '=' + shared.JS.make_coercion('a' + str(i), sig[i]) + ';' for i in range(1, len(sig))]) coerced_args = ','.join([shared.JS.make_coercion('a' + str(i), sig[i]) for i in range(1, len(sig))]) sig_mask = str(table.count(',')) - ret = ('return ' if sig[0] != 'v' else '') + shared.JS.make_coercion('FUNCTION_TABLE_%s[index&%s](%s)' % (sig, sig_mask, coerced_args), sig[0]) - if not shared.Settings.EMULATED_FUNCTION_POINTERS: + if not (shared.Settings.WASM and shared.Settings.EMULATED_FUNCTION_POINTERS): + ret = 'FUNCTION_TABLE_%s[index&%s](%s)' % (sig, sig_mask, coerced_args) + else: + # for wasm with emulated function pointers, emit an mft_SIG(..) call, we avoid asm.js function tables there. + ret = 'mftCall_%s(index%s%s)' % (sig, ',' if len(sig) > 1 else '', coerced_args) + ret = ('return ' if sig[0] != 'v' else '') + shared.JS.make_coercion(ret, sig[0]) + if not asm_js_emulated_function_pointers(): function_tables_impls.append(''' function dynCall_%s(index%s%s) { index = index|0; @@ -1199,7 +1211,7 @@ def make_function_tables_impls(function_table_data): def create_mftCall_funcs(function_table_data): - if not shared.Settings.EMULATED_FUNCTION_POINTERS: + if not asm_js_emulated_function_pointers(): return [] if shared.Settings.WASM or not shared.Settings.RELOCATABLE: return [] @@ -1488,40 +1500,24 @@ def setup_function_pointers(function_table_sigs): for sig in function_table_sigs: if shared.Settings.RESERVED_FUNCTION_POINTERS: asm_setup += '\n' + shared.JS.make_jscall(sig) + '\n' - if shared.Settings.EMULATED_FUNCTION_POINTERS: - args = ['a%d' % i for i in range(len(sig) - 1)] - full_args = ['x'] + args - if shared.Settings.WASM: - if shared.Settings.EMULATE_FUNCTION_POINTER_CASTS: - # emulated function pointers in wasm use an internal i64-based ABI with a fixed number of arguments. we can't - # call into it directly because it returns an i64, which is an error for the VM. instead, we use dynCalls - dyn_call = "Module['asm']['dynCall_" + sig + "']" - asm_setup += ''' -function ftCall_%s(%s) { - return %s(%s); -} -''' % (sig, ', '.join(full_args), dyn_call, ', '.join(full_args)) - # and we are done with this signature, continue - continue - else: - # otherwise, wasm emulated function pointers *without* emulated casts can just all - # into the table - table_access = "wasmTable" - table_read = table_access + '.get(x)' - else: + # nothing special to do here for wasm, we just use dynCalls + if not shared.Settings.WASM: + if shared.Settings.EMULATED_FUNCTION_POINTERS: + args = ['a%d' % i for i in range(len(sig) - 1)] + full_args = ['x'] + args table_access = 'FUNCTION_TABLE_' + sig if shared.Settings.SIDE_MODULE: table_access = 'parentModule["' + table_access + '"]' # side module tables were merged into the parent, we need to access the global one table_read = table_access + '[x]' - prelude = '' - if shared.Settings.ASSERTIONS: - prelude = ''' - if (x < 0 || x >= %s.length) { err("Function table mask error (out of range)"); %s ; abort(x) }''' % (table_access, get_function_pointer_error(sig, function_table_sigs)) - asm_setup += ''' -function ftCall_%s(%s) {%s - return %s(%s); -} -''' % (sig, ', '.join(full_args), prelude, table_read, ', '.join(args)) + prelude = '' + if shared.Settings.ASSERTIONS: + prelude = ''' + if (x < 0 || x >= %s.length) { err("Function table mask error (out of range)"); %s ; abort(x) }''' % (table_access, get_function_pointer_error(sig, function_table_sigs)) + asm_setup += ''' + function ftCall_%s(%s) {%s + return %s(%s); + } + ''' % (sig, ', '.join(full_args), prelude, table_read, ', '.join(args)) return asm_setup @@ -1545,10 +1541,8 @@ def create_basic_funcs(function_table_sigs, invoke_function_names): for sig in function_table_sigs: if shared.Settings.RESERVED_FUNCTION_POINTERS: basic_funcs.append('jsCall_%s' % sig) - if shared.Settings.EMULATED_FUNCTION_POINTERS: - # in wasm, emulated function pointers are just simple table calls - if not shared.Settings.WASM: - basic_funcs.append('ftCall_%s' % sig) + if asm_js_emulated_function_pointers(): + basic_funcs.append('ftCall_%s' % sig) return basic_funcs @@ -1615,7 +1609,7 @@ def create_asm_runtime_funcs(): def function_tables(function_table_data): - if not shared.Settings.EMULATED_FUNCTION_POINTERS: + if not asm_js_emulated_function_pointers(): return ['dynCall_' + table for table in function_table_data] else: return [] diff --git a/src/parseTools.js b/src/parseTools.js index f6b66d8fa5da4..0cc9c15210c99 100644 --- a/src/parseTools.js +++ b/src/parseTools.js @@ -1455,10 +1455,13 @@ function asmFFICoercion(value, type) { } function makeDynCall(sig) { - if (!EMULATED_FUNCTION_POINTERS) { - return 'dynCall_' + sig; - } else { + // asm.js function tables have one table in each linked asm.js module, so we + // can't just dynCall into them - ftCall exists for that purpose. In wasm, + // even linked modules share the table, so it's all fine. + if (EMULATED_FUNCTION_POINTERS && !WASM) { return 'ftCall_' + sig; + } else { + return 'dynCall_' + sig; } } diff --git a/tests/other/noffi.cpp b/tests/other/noffi.cpp new file mode 100644 index 0000000000000..4dddafe574a8f --- /dev/null +++ b/tests/other/noffi.cpp @@ -0,0 +1,36 @@ +/* + * Copyright 2019 The Emscripten Authors. All rights reserved. + * Emscripten is available under two separate licenses, the MIT license and the + * University of Illinois/NCSA Open Source License. Both these licenses can be + * found in the LICENSE file. + */ + +#include +#include +#include +#include +#include +#include + +using namespace std; + +uint64_t getbigint(){ + int ran = rand() % 100;// v1 in the range 0 to 99 + ++ran; + if(ran > -1) + throw new std::runtime_error("error!!"); + + return 1152921504606846975 + ran; +} +int main() +{ + float safeY = 0.0f; + uint64_t mybig = 0; + + try{ + mybig = getbigint(); + } + catch(std::runtime_error){}; + + return 0; +} \ No newline at end of file diff --git a/tests/test_other.py b/tests/test_other.py index 4249e18df19d8..2287b112d1e1c 100644 --- a/tests/test_other.py +++ b/tests/test_other.py @@ -7979,7 +7979,7 @@ def test_binaryen_metadce_cxx(self): self.run_metadce_tests(path_from_root('tests', 'hello_libcxx.cpp'), [ (['-O2'], 34, ['abort'], ['waka'], 196709, 28, 37, 660), # noqa (['-O2', '-s', 'EMULATED_FUNCTION_POINTERS=1'], - 34, ['abort'], ['waka'], 196709, 28, 18, 621), # noqa + 34, ['abort'], ['waka'], 196709, 28, 38, 642), # noqa ]) # noqa def test_binaryen_metadce_hello(self): @@ -8008,7 +8008,7 @@ def test_binaryen_metadce_hello(self): 0, [], [], 8, 0, 0, 0), # noqa; totally empty! # we don't metadce with linkable code! other modules may want stuff (['-O3', '-s', 'MAIN_MODULE=1'], - 1533, [], [], 226057, 28, 85, None), # noqa; don't compare the # of functions in a main module, which changes a lot + 1533, [], [], 226403, 28, 93, None), # noqa; don't compare the # of functions in a main module, which changes a lot ]) # noqa # ensures runtime exports work, even with metadce @@ -8070,6 +8070,34 @@ def test_legalize_js_ffi(self): assert not e_f32_f64, 'f32 converted to f64 in exports' assert e_i64_i64, 'i64 converted to i64 in exports' + def test_no_legalize_js_ffi(self): + # test minimal JS FFI legalization for invoke and dyncalls + if self.is_wasm_backend(): + self.skipTest('not testing legalize with main module and wasm backend') + wasm_dis = os.path.join(Building.get_binaryen_bin(), 'wasm-dis') + for (args, js_ffi) in [ + (['-s', 'LEGALIZE_JS_FFI=0', '-s', 'MAIN_MODULE=2', '-O3', '-s', 'DISABLE_EXCEPTION_CATCHING=0'], False), + ]: + print(args) + try_delete('a.out.wasm') + try_delete('a.out.wast') + with env_modify({'EMCC_FORCE_STDLIBS': 'libc++'}): + cmd = [PYTHON, EMCC, path_from_root('tests', 'other', 'noffi.cpp'), '-g', '-o', 'a.out.js'] + args + print(' '.join(cmd)) + run_process(cmd) + run_process([wasm_dis, 'a.out.wasm', '-o', 'a.out.wast']) + text = open('a.out.wast').read() + # remove internal comments and extra whitespace + text = re.sub(r'\(;[^;]+;\)', '', text) + text = re.sub(r'\$var\$*.', '', text) + text = re.sub(r'param \$\d+', 'param ', text) + text = re.sub(r' +', ' ', text) + # print("text: %s" % text) + i_legalimport_i64 = re.search('\(import.*\$legalimport\$invoke_j.*', text) + e_legalstub_i32 = re.search('\(func.*\$legalstub\$dyn.*\(type \$\d+\).*\(result i32\)', text) + assert i_legalimport_i64, 'legal import not generated for invoke call' + assert e_legalstub_i32, 'legal stub not generated for dyncall' + def test_sysconf_phys_pages(self): for args, expected in [ ([], 1024),