From 4550d31822c86abbd8ad71a0473115ec4a11cc0b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jukka=20Jyl=C3=A4nki?= Date: Fri, 10 Nov 2017 18:13:30 +0200 Subject: [PATCH 1/6] Add a placeholder PTHREADS_DEBUG=0/1 variable for debugging pthreads execution. --- src/settings.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/settings.js b/src/settings.js index ccc0f39ded3ef..341f0aca18a72 100644 --- a/src/settings.js +++ b/src/settings.js @@ -795,6 +795,8 @@ var PTHREAD_HINT_NUM_CORES = 4; var PTHREADS_PROFILING = 0; // True when building with --threadprofiler +var PTHREADS_DEBUG = 0; // If true, add in debug traces for diagnosing pthreads related issues. + var MAX_GLOBAL_ALIGN = -1; // received from the backend // Duplicate function elimination. This coalesces function bodies that are From aef7471d0b548e6deca18346d629a1110612fb50 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jukka=20Jyl=C3=A4nki?= Date: Sat, 26 Aug 2017 15:06:32 +0300 Subject: [PATCH 2/6] Implement MAIN_THREAD_EM_ASM() and MAIN_THREAD_ASYNC_EM_ASM() macros that allow synchronous and asynchronous proxying of EM_ASM calls to main browser thread. --- emscripten.py | 34 ++++++++++++++++++++++------ src/library.js | 3 +++ system/include/emscripten/em_asm.h | 36 +++++++++++++++++++++++++++--- 3 files changed, 63 insertions(+), 10 deletions(-) diff --git a/emscripten.py b/emscripten.py index 45ddd1aaf302f..3c13046fa1fa5 100644 --- a/emscripten.py +++ b/emscripten.py @@ -642,27 +642,44 @@ def get_exported_implemented_functions(all_exported_functions, all_implemented, def get_implemented_functions(metadata): return set(metadata['implementedFunctions']) +def proxy_debug_print(call_type, settings): + if shared.Settings.PTHREADS_DEBUG: + if call_type == 'sync_on_main_thread_': return 'Runtime.warnOnce("sync proxying function " + code);'; + elif call_type == 'async_on_main_thread_': return 'Runtime.warnOnce("async proxying function " + code);'; + return '' def include_asm_consts(pre, forwarded_json, metadata, settings): if settings['BINARYEN'] and settings['SIDE_MODULE']: assert len(metadata['asmConsts']) == 0, 'EM_ASM is not yet supported in shared wasm module (it cannot be stored in the wasm itself, need some solution)' - asm_consts, all_sigs = all_asm_consts(metadata) + asm_consts, all_sigs, call_types = all_asm_consts(metadata) asm_const_funcs = [] - for sig in set(all_sigs): - forwarded_json['Functions']['libraryFunctions']['_emscripten_asm_const_' + sig] = 1 + for s in range(len(all_sigs)): + sig = all_sigs[s] + call_type = call_types[s] if s < len(call_types) else '' + forwarded_json['Functions']['libraryFunctions']['_emscripten_asm_const_' + call_type + sig] = 1 args = ['a%d' % i for i in range(len(sig)-1)] all_args = ['code'] + args + proxy_function = '' + if shared.Settings.USE_PTHREADS: + if call_type == 'sync_on_main_thread_': proxy_function = '_emscripten_sync_run_in_browser_thread_' + sig + elif call_type == 'async_on_main_thread_': proxy_function = '_emscripten_async_run_in_browser_thread_' + sig + + # In proxied function calls, positive integers 1, 2, 3, ... denote pointers to regular C compiled functions. Negative integers -1, -2, -3, ... denote indices to EM_ASM() blocks, so remap the EM_ASM() indices from 0, 1, 2, ... over to the negative integers starting at -1. + proxy_args = '-1 - ' + ','.join(all_args) + + if proxy_function: proxy_to_main_thread = ' if (ENVIRONMENT_IS_PTHREAD) { ' + proxy_debug_print(call_type, settings) + 'return ' + proxy_function + '(' + proxy_args + '); } \n' + else: proxy_to_main_thread = '' asm_const_funcs.append(r''' function _emscripten_asm_const_%s(%s) { - return ASM_CONSTS[code](%s); -}''' % (sig.encode('utf-8'), ', '.join(all_args), ', '.join(args))) +%s return ASM_CONSTS[code](%s); +}''' % (call_type + sig.encode('utf-8'), ', '.join(all_args), proxy_to_main_thread, ', '.join(args))) asm_consts_text = '\nvar ASM_CONSTS = [' + ',\n '.join(asm_consts) + '];\n' asm_funcs_text = '\n'.join(asm_const_funcs) + '\n' body_marker = '// === Body ===' - return pre.replace(body_marker, body_marker + '\n' + asm_consts_text + asm_funcs_text) + return pre.replace(body_marker, body_marker + '\n' + asm_consts_text + asm_funcs_text.encode('utf-8')) # Test if the parentheses at body[openIdx] and body[closeIdx] are a match to each other. def parentheses_match(body, openIdx, closeIdx): @@ -690,9 +707,11 @@ def trim_asm_const_body(body): def all_asm_consts(metadata): asm_consts = [0]*len(metadata['asmConsts']) all_sigs = [] + all_call_types = [] for k, v in metadata['asmConsts'].items(): const = v[0].encode('utf-8') sigs = v[1] + call_types = v[2] const = trim_asm_const_body(const) const = '{ ' + const + ' }' args = [] @@ -702,7 +721,8 @@ def all_asm_consts(metadata): const = 'function(' + ', '.join(args) + ') ' + const asm_consts[int(k)] = const all_sigs += sigs - return asm_consts, all_sigs + all_call_types += call_types + return asm_consts, all_sigs, all_call_types def unfloat(s): diff --git a/src/library.js b/src/library.js index f7eca74b91a27..6ab358296d4d5 100644 --- a/src/library.js +++ b/src/library.js @@ -4372,6 +4372,9 @@ LibraryManager.library = { emscripten_asm_const: true, emscripten_asm_const_int: true, emscripten_asm_const_double: true, + emscripten_asm_const_int_sync_on_main_thread: true, + emscripten_asm_const_double_sync_on_main_thread: true, + emscripten_asm_const_async_on_main_thread: true, // ======== compiled code from system/lib/compiler-rt , see readme therein __muldsi3__asm: true, diff --git a/system/include/emscripten/em_asm.h b/system/include/emscripten/em_asm.h index 41efe0f98f48b..b3374989373e8 100644 --- a/system/include/emscripten/em_asm.h +++ b/system/include/emscripten/em_asm.h @@ -32,6 +32,12 @@ extern "C" { void emscripten_asm_const(const char* code); int emscripten_asm_const_int(const char* code, ...); double emscripten_asm_const_double(const char* code, ...); + +int emscripten_asm_const_int_sync_on_main_thread(const char* code, ...); +double emscripten_asm_const_double_sync_on_main_thread(const char* code, ...); + +void emscripten_asm_const_async_on_main_thread(const char* code, ...); + #ifdef __cplusplus } #endif @@ -43,15 +49,39 @@ void emscripten_asm_const(const char* code); // then wrap the whole code block inside parentheses (). See tests/core/test_em_asm_2.cpp // for example code snippets. -// Runs the given JavaScript code, and returns nothing back. +// Runs the given JavaScript code on the calling thread (synchronously), and returns no value back. #define EM_ASM(code, ...) ((void)emscripten_asm_const_int(#code, ##__VA_ARGS__)) -// Runs the given JavaScript code, and returns an integer back. +// Runs the given JavaScript code on the calling thread (synchronously), and returns an integer back. #define EM_ASM_INT(code, ...) emscripten_asm_const_int(#code, ##__VA_ARGS__) -// Runs the given JavaScript code, and returns a double back. +// Runs the given JavaScript code on the calling thread (synchronously), and returns a double back. #define EM_ASM_DOUBLE(code, ...) emscripten_asm_const_double(#code, ##__VA_ARGS__) +// Runs the given JavaScript code synchronously on the main browser thread, and returns no value back. +// Call this function for example to access DOM elements in a pthread/web worker. Avoid calling this +// function in performance sensitive code, because this will effectively sleep the calling thread until the +// main browser thread is able to service the proxied function call. If you have multiple MAIN_THREAD_EM_ASM() +// code blocks to call in succession, it will likely be much faster to coalesce all the calls to a single +// MAIN_THREAD_EM_ASM() block. If you do not need synchronization nor a return value back, consider using +// the function MAIN_THREAD_ASYNC_EM_ASM() instead, which will not block. +#define MAIN_THREAD_EM_ASM(code, ...) ((void)emscripten_asm_const_int_sync_on_main_thread(#code, ##__VA_ARGS__)) + +// Runs the given JavaScript code synchronously on the main browser thread, and returns an integer back. +// The same considerations apply as with MAIN_THREAD_EM_ASM(). +#define MAIN_THREAD_EM_ASM_INT(code, ...) emscripten_asm_const_int_sync_on_main_thread(#code, ##__VA_ARGS__) + +// Runs the given JavaScript code synchronously on the main browser thread, and returns a double back. +// The same considerations apply as with MAIN_THREAD_EM_ASM(). +#define MAIN_THREAD_EM_ASM_DOUBLE(code, ...) emscripten_asm_const_double_sync_on_main_thread(#code, ##__VA_ARGS__) + +// Asynchronously dispatches the given JavaScript code to be run on the main browser thread. +// If the calling thread is the main browser thread, then the specified JavaScript code is executed +// synchronously. Otherwise an event will be queued on the main browser thread to execute the call +// later (think postMessage()), and this call will immediately return without waiting. Be sure to +// guard any accesses to shared memory on the heap inside the JavaScript code with appropriate locking. +#define MAIN_THREAD_ASYNC_EM_ASM(code, ...) ((void)emscripten_asm_const_async_on_main_thread(#code, ##__VA_ARGS__)) + // Old forms for compatibility, no need to use these. // Replace EM_ASM_, EM_ASM_ARGS and EM_ASM_INT_V with EM_ASM_INT, // and EM_ASM_DOUBLE_V with EM_ASM_DOUBLE. From ce2eec97cf51b89a36eee4b5b53d335807b0e2ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jukka=20Jyl=C3=A4nki?= Date: Sun, 27 Aug 2017 14:42:59 +0300 Subject: [PATCH 3/6] Add singlethreaded testing for MAIN_THREAD_EM_ASM and MAIN_THREAD_ASYNC_EM_ASM. --- tests/core/test_main_thread_async_em_asm.cpp | 11 +++++++++++ tests/core/test_main_thread_async_em_asm.out | 3 +++ tests/test_core.py | 20 ++++++++++++++++++++ 3 files changed, 34 insertions(+) create mode 100644 tests/core/test_main_thread_async_em_asm.cpp create mode 100644 tests/core/test_main_thread_async_em_asm.out diff --git a/tests/core/test_main_thread_async_em_asm.cpp b/tests/core/test_main_thread_async_em_asm.cpp new file mode 100644 index 0000000000000..621caa35c7572 --- /dev/null +++ b/tests/core/test_main_thread_async_em_asm.cpp @@ -0,0 +1,11 @@ +#include +#include + +int main() +{ + // Test that on main browser thread, MAIN_THREAD_ASYNC_EM_ASM() will get + // synchronously executed. + printf("Before MAIN_THREAD_ASYNC_EM_ASM\n"); + MAIN_THREAD_ASYNC_EM_ASM(Module.print('Inside MAIN_THREAD_ASYNC_EM_ASM: ' + $0 + ' ' + $1), 42, 3.5); + printf("After MAIN_THREAD_ASYNC_EM_ASM\n"); +} diff --git a/tests/core/test_main_thread_async_em_asm.out b/tests/core/test_main_thread_async_em_asm.out new file mode 100644 index 0000000000000..47ebc8fad5419 --- /dev/null +++ b/tests/core/test_main_thread_async_em_asm.out @@ -0,0 +1,3 @@ +Before MAIN_THREAD_ASYNC_EM_ASM +Inside MAIN_THREAD_ASYNC_EM_ASM: 42 3.5 +After MAIN_THREAD_ASYNC_EM_ASM diff --git a/tests/test_core.py b/tests/test_core.py index 16c97c43624e6..37752e24e4cf1 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -1603,10 +1603,30 @@ def test_em_asm(self): self.do_run_in_out_file_test('tests', 'core', 'test_em_asm') self.do_run_in_out_file_test('tests', 'core', 'test_em_asm', force_c=True) + # Tests various different ways to invoke the EM_ASM(), EM_ASM_INT() and EM_ASM_DOUBLE() macros. def test_em_asm_2(self): self.do_run_in_out_file_test('tests', 'core', 'test_em_asm_2') self.do_run_in_out_file_test('tests', 'core', 'test_em_asm_2', force_c=True) + # Tests various different ways to invoke the MAIN_THREAD_EM_ASM(), MAIN_THREAD_EM_ASM_INT() and MAIN_THREAD_EM_ASM_DOUBLE() macros. + # This test is identical to test_em_asm_2, just search-replaces EM_ASM to MAIN_THREAD_EM_ASM on the test file. That way if new + # test cases are added to test_em_asm_2.cpp for EM_ASM, they will also get tested in MAIN_THREAD_EM_ASM form. + def test_main_thread_em_asm(self): + src = open(path_from_root('tests', 'core', 'test_em_asm_2.cpp'), 'r').read() + test_file = 'src.cpp' + open(test_file, 'w').write(src.replace('EM_ASM', 'MAIN_THREAD_EM_ASM')) + + expected_result = open(path_from_root('tests', 'core', 'test_em_asm_2.out'), 'r').read() + expected_result_file = 'result.out' + open(expected_result_file, 'w').write(expected_result.replace('EM_ASM', 'MAIN_THREAD_EM_ASM')) + + self.do_run_from_file(test_file, expected_result_file) + self.do_run_from_file(test_file, expected_result_file, force_c=True) + + def test_main_thread_async_em_asm(self): + self.do_run_in_out_file_test('tests', 'core', 'test_main_thread_async_em_asm') + self.do_run_in_out_file_test('tests', 'core', 'test_main_thread_async_em_asm', force_c=True) + def test_em_asm_unicode(self): self.do_run(r''' #include From 8a058f57167581cf55495f0f73c770f04671c482 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jukka=20Jyl=C3=A4nki?= Date: Sun, 27 Aug 2017 21:35:07 +0300 Subject: [PATCH 4/6] Show the usage of MAIN_THREAD_EM_ASM() in documentation on MEMFS. --- site/source/docs/api_reference/Filesystem-API.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/site/source/docs/api_reference/Filesystem-API.rst b/site/source/docs/api_reference/Filesystem-API.rst index fc1a9d113af38..fc51768937d40 100644 --- a/site/source/docs/api_reference/Filesystem-API.rst +++ b/site/source/docs/api_reference/Filesystem-API.rst @@ -312,7 +312,7 @@ File system API #include int main() { - EM_ASM( + MAIN_THREAD_EM_ASM( FS.writeFile('file', 'foobar'); FS.symlink('file', 'link'); console.log(FS.readlink('link')); @@ -341,7 +341,7 @@ File system API #include int main() { - EM_ASM( + MAIN_THREAD_EM_ASM( FS.writeFile('file', 'foobar'); console.log(FS.stat('file')); ); @@ -459,7 +459,7 @@ File system API #include int main() { - EM_ASM( + MAIN_THREAD_EM_ASM( FS.writeFile('file', 'foobar'); FS.truncate('file', 3); console.log(FS.readFile('file', { encoding: 'utf8' })); From d9f59d5cd91e7fd2d402fb936b40535f2170cf0c Mon Sep 17 00:00:00 2001 From: Jukka Jylanki Date: Wed, 22 Nov 2017 14:07:34 +0200 Subject: [PATCH 5/6] Improve async EM_ASM handling to not fail if executed on old compiler version that does not specify the call types --- emscripten.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/emscripten.py b/emscripten.py index 3c13046fa1fa5..0a0c4fe84f9bd 100644 --- a/emscripten.py +++ b/emscripten.py @@ -711,7 +711,7 @@ def all_asm_consts(metadata): for k, v in metadata['asmConsts'].items(): const = v[0].encode('utf-8') sigs = v[1] - call_types = v[2] + call_types = v[2] if len(v) >= 3 else None const = trim_asm_const_body(const) const = '{ ' + const + ' }' args = [] @@ -721,7 +721,7 @@ def all_asm_consts(metadata): const = 'function(' + ', '.join(args) + ') ' + const asm_consts[int(k)] = const all_sigs += sigs - all_call_types += call_types + if call_types: all_call_types += call_types return asm_consts, all_sigs, all_call_types From e7d9ddf5c9115ae60ebb433a14ec4af0125085ce Mon Sep 17 00:00:00 2001 From: Jukka Jylanki Date: Wed, 22 Nov 2017 14:09:01 +0200 Subject: [PATCH 6/6] Mark test_main_thread_em_asm and test_main_thread_async_em_asm tests @no_wasm_backend --- tests/test_core.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/test_core.py b/tests/test_core.py index 37752e24e4cf1..7939eeb4b437c 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -1611,6 +1611,7 @@ def test_em_asm_2(self): # Tests various different ways to invoke the MAIN_THREAD_EM_ASM(), MAIN_THREAD_EM_ASM_INT() and MAIN_THREAD_EM_ASM_DOUBLE() macros. # This test is identical to test_em_asm_2, just search-replaces EM_ASM to MAIN_THREAD_EM_ASM on the test file. That way if new # test cases are added to test_em_asm_2.cpp for EM_ASM, they will also get tested in MAIN_THREAD_EM_ASM form. + @@no_wasm_backend def test_main_thread_em_asm(self): src = open(path_from_root('tests', 'core', 'test_em_asm_2.cpp'), 'r').read() test_file = 'src.cpp' @@ -1623,6 +1624,7 @@ def test_main_thread_em_asm(self): self.do_run_from_file(test_file, expected_result_file) self.do_run_from_file(test_file, expected_result_file, force_c=True) + @no_wasm_backend def test_main_thread_async_em_asm(self): self.do_run_in_out_file_test('tests', 'core', 'test_main_thread_async_em_asm') self.do_run_in_out_file_test('tests', 'core', 'test_main_thread_async_em_asm', force_c=True)