From b9c24785c39aa7264c493d1ff774d5a04b4ca976 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 18 Aug 2015 16:01:22 -0700 Subject: [PATCH] clarify the extra optimization for function pointers that we do in shared modules by default, by creating an EMULATED_FUNCTION_POINTERS=2 setting, in which we optimize, and 1 does not, and still allows for full flexibility in manipulating the function table from JS --- emcc | 3 ++- emscripten.py | 6 +++++- src/settings.js | 10 +++++++++ tests/test_core.py | 2 +- tests/test_other.py | 51 +++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 69 insertions(+), 3 deletions(-) diff --git a/emcc b/emcc index 1abf0057e..078c607c4 100755 --- a/emcc +++ b/emcc @@ -953,7 +953,8 @@ try: if shared.Settings.RELOCATABLE: assert shared.Settings.GLOBAL_BASE < 1 - shared.Settings.EMULATED_FUNCTION_POINTERS = 1 + if shared.Settings.EMULATED_FUNCTION_POINTERS == 0: + shared.Settings.EMULATED_FUNCTION_POINTERS = 2 # by default, use optimized function pointer emulation shared.Settings.ERROR_ON_UNDEFINED_SYMBOLS = shared.Settings.WARN_ON_UNDEFINED_SYMBOLS = 0 if not shared.Settings.SIDE_MODULE: shared.Settings.EXPORT_ALL = 1 diff --git a/emscripten.py b/emscripten.py index bf8c47fc0..53ff8da3e 100755 --- a/emscripten.py +++ b/emscripten.py @@ -617,7 +617,11 @@ function ftCall_%s(%s) {%s coercions = ';'.join(['ptr = ptr | 0'] + ['p%d = %s' % (p, shared.JS.make_coercion('p%d' % p, sig[p+1], settings)) for p in range(len(sig)-1)]) + ';' mini_coerced_params = make_coerced_params(sig) maybe_return = '' if sig[0] == 'v' else 'return' - funcs_js.append(make_func('mftCall_' + sig, 'if (((ptr|0) >= (fb|0)) & ((ptr|0) < (fb + {{{ FTM_' + sig + ' }}} | 0))) { ' + maybe_return + ' ' + shared.JS.make_coercion('FUNCTION_TABLE_' + sig + '[(ptr-fb)&{{{ FTM_' + sig + ' }}}](' + mini_coerced_params + ')', sig[0], settings) + '; ' + ('return;' if sig[0] == 'v' else '') + ' }' + maybe_return + ' ' + shared.JS.make_coercion('ftCall_' + sig + '(' + coerced_params + ')', sig[0], settings) + ';', params, coercions) + '\n') + if settings['EMULATED_FUNCTION_POINTERS'] == 1: + body = maybe_return + ' ' + shared.JS.make_coercion('ftCall_' + sig + '(' + coerced_params + ')', sig[0], settings) + ';' + else: + body = 'if (((ptr|0) >= (fb|0)) & ((ptr|0) < (fb + {{{ FTM_' + sig + ' }}} | 0))) { ' + maybe_return + ' ' + shared.JS.make_coercion('FUNCTION_TABLE_' + sig + '[(ptr-fb)&{{{ FTM_' + sig + ' }}}](' + mini_coerced_params + ')', sig[0], settings) + '; ' + ('return;' if sig[0] == 'v' else '') + ' }' + maybe_return + ' ' + shared.JS.make_coercion('ftCall_' + sig + '(' + coerced_params + ')', sig[0], settings) + ';' + funcs_js.append(make_func('mftCall_' + sig, body, params, coercions) + '\n') def quote(prop): if settings['USE_CLOSURE_COMPILER'] == 2: diff --git a/src/settings.js b/src/settings.js index 17192918e..263320414 100644 --- a/src/settings.js +++ b/src/settings.js @@ -176,6 +176,16 @@ var EMULATED_FUNCTION_POINTERS = 0; // By default we implement function pointers // function tables, which is very fast. With this option, // we implement them more flexibly by emulating them: we // call out into JS, which handles the function tables. + // 1: Full emulation. This means you can modify the + // table in JS fully dynamically, not just add to + // the end. + // 2: Optimized emulation. Assumes once something is + // added to the table, it will not change. This allows + // dynamic linking while keeping performance fast, + // as we can do a fast call into the internal table + // if the fp is in the right range. Shared modules + // (MAIN_MODULE, SIDE_MODULE) do this by default. + // This requires RELOCATABLE to be set. var EMULATE_FUNCTION_POINTER_CASTS = 0; // Allows function pointers to be cast, wraps each // call of an incorrect type with a runtime correction. // This adds overhead and should not be used normally. diff --git a/tests/test_core.py b/tests/test_core.py index f12c787fb..3b8f7f201 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -465,7 +465,7 @@ class T(RunnerCore): # Short name, to make it more fun to use manually on the co # extra coverages for emulate_casts in [0, 1]: - for emulate_fps in [0, 1]: + for emulate_fps in [0, 1, 2]: print emulate_casts, emulate_fps Settings.EMULATE_FUNCTION_POINTER_CASTS = emulate_casts Settings.EMULATED_FUNCTION_POINTERS = emulate_fps diff --git a/tests/test_other.py b/tests/test_other.py index 34aaf1a40..17fe741ab 100644 --- a/tests/test_other.py +++ b/tests/test_other.py @@ -4933,6 +4933,57 @@ int main(int argc, char** argv) { test(['-O' + str(opts)], 'no visible function tables') test(['-O' + str(opts), '-s', 'EMULATED_FUNCTION_POINTERS=1'], 'function table: ') + def test_emulated_function_pointers_2(self): + src = r''' + #include + typedef void (*fp)(); + void one() { EM_ASM( Module.print('one') ); } + void two() { EM_ASM( Module.print('two') ); } + void test() { + volatile fp f = one; + f(); + f = two; + f(); + } + int main(int argc, char **argv) { + test(); + // swap them! + EM_ASM_INT({ + var one = $0; + var two = $1; + if (typeof FUNCTION_TABLE_v === 'undefined') { + Module.print('no'); + return; + } + var temp = FUNCTION_TABLE_v[one]; + FUNCTION_TABLE_v[one] = FUNCTION_TABLE_v[two]; + FUNCTION_TABLE_v[two] = temp; + }, (int)&one, (int)&two); + test(); + return 0; + } + ''' + open('src.c', 'w').write(src) + + flipped = 'one\ntwo\ntwo\none\n' + unchanged = 'one\ntwo\none\ntwo\n' + no_table = 'one\ntwo\nno\none\ntwo\n' + + def test(args, expected): + print args, expected.replace('\n', ' ') + Popen([PYTHON, EMCC, 'src.c'] + args).communicate() + self.assertContained(expected, run_js(self.in_dir('a.out.js'))) + + for opts in [0, 1, 2]: + test(['-O' + str(opts)], no_table) + test(['-O' + str(opts), '-s', 'EMULATED_FUNCTION_POINTERS=1'], flipped) + test(['-O' + str(opts), '-s', 'EMULATED_FUNCTION_POINTERS=2'], flipped) + test(['-O' + str(opts), '-s', 'EMULATED_FUNCTION_POINTERS=1', '-s', 'RELOCATABLE=1'], flipped) + test(['-O' + str(opts), '-s', 'EMULATED_FUNCTION_POINTERS=2', '-s', 'RELOCATABLE=1'], unchanged) # with both of those, we optimize and you cannot flip them + test(['-O' + str(opts), '-s', 'MAIN_MODULE=1'], unchanged) # default for modules is optimized + test(['-O' + str(opts), '-s', 'MAIN_MODULE=1', '-s', 'EMULATED_FUNCTION_POINTERS=2'], unchanged) + test(['-O' + str(opts), '-s', 'MAIN_MODULE=1', '-s', 'EMULATED_FUNCTION_POINTERS=1'], flipped) # but you can disable that + def test_file_packager_eval(self): BAD = 'Module = eval(' src = path_from_root('tests', 'hello_world.c')