Skip to content

Commit ea92fdd

Browse files
authored
Merge pull request #5544 from juj/em_asm_on_main_thread
Multithreading 9/N: MAIN_THREAD_EM_ASM()
2 parents a187a6a + e7d9ddf commit ea92fdd

File tree

8 files changed

+104
-13
lines changed

8 files changed

+104
-13
lines changed

emscripten.py

Lines changed: 27 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -642,27 +642,44 @@ def get_exported_implemented_functions(all_exported_functions, all_implemented,
642642
def get_implemented_functions(metadata):
643643
return set(metadata['implementedFunctions'])
644644

645+
def proxy_debug_print(call_type, settings):
646+
if shared.Settings.PTHREADS_DEBUG:
647+
if call_type == 'sync_on_main_thread_': return 'Runtime.warnOnce("sync proxying function " + code);';
648+
elif call_type == 'async_on_main_thread_': return 'Runtime.warnOnce("async proxying function " + code);';
649+
return ''
645650

646651
def include_asm_consts(pre, forwarded_json, metadata, settings):
647652
if settings['BINARYEN'] and settings['SIDE_MODULE']:
648653
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)'
649654

650-
asm_consts, all_sigs = all_asm_consts(metadata)
655+
asm_consts, all_sigs, call_types = all_asm_consts(metadata)
651656
asm_const_funcs = []
652-
for sig in set(all_sigs):
653-
forwarded_json['Functions']['libraryFunctions']['_emscripten_asm_const_' + sig] = 1
657+
for s in range(len(all_sigs)):
658+
sig = all_sigs[s]
659+
call_type = call_types[s] if s < len(call_types) else ''
660+
forwarded_json['Functions']['libraryFunctions']['_emscripten_asm_const_' + call_type + sig] = 1
654661
args = ['a%d' % i for i in range(len(sig)-1)]
655662
all_args = ['code'] + args
663+
proxy_function = ''
664+
if shared.Settings.USE_PTHREADS:
665+
if call_type == 'sync_on_main_thread_': proxy_function = '_emscripten_sync_run_in_browser_thread_' + sig
666+
elif call_type == 'async_on_main_thread_': proxy_function = '_emscripten_async_run_in_browser_thread_' + sig
667+
668+
# 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.
669+
proxy_args = '-1 - ' + ','.join(all_args)
670+
671+
if proxy_function: proxy_to_main_thread = ' if (ENVIRONMENT_IS_PTHREAD) { ' + proxy_debug_print(call_type, settings) + 'return ' + proxy_function + '(' + proxy_args + '); } \n'
672+
else: proxy_to_main_thread = ''
656673
asm_const_funcs.append(r'''
657674
function _emscripten_asm_const_%s(%s) {
658-
return ASM_CONSTS[code](%s);
659-
}''' % (sig.encode('utf-8'), ', '.join(all_args), ', '.join(args)))
675+
%s return ASM_CONSTS[code](%s);
676+
}''' % (call_type + sig.encode('utf-8'), ', '.join(all_args), proxy_to_main_thread, ', '.join(args)))
660677

661678
asm_consts_text = '\nvar ASM_CONSTS = [' + ',\n '.join(asm_consts) + '];\n'
662679
asm_funcs_text = '\n'.join(asm_const_funcs) + '\n'
663680

664681
body_marker = '// === Body ==='
665-
return pre.replace(body_marker, body_marker + '\n' + asm_consts_text + asm_funcs_text)
682+
return pre.replace(body_marker, body_marker + '\n' + asm_consts_text + asm_funcs_text.encode('utf-8'))
666683

667684
# Test if the parentheses at body[openIdx] and body[closeIdx] are a match to each other.
668685
def parentheses_match(body, openIdx, closeIdx):
@@ -690,9 +707,11 @@ def trim_asm_const_body(body):
690707
def all_asm_consts(metadata):
691708
asm_consts = [0]*len(metadata['asmConsts'])
692709
all_sigs = []
710+
all_call_types = []
693711
for k, v in metadata['asmConsts'].items():
694712
const = v[0].encode('utf-8')
695713
sigs = v[1]
714+
call_types = v[2] if len(v) >= 3 else None
696715
const = trim_asm_const_body(const)
697716
const = '{ ' + const + ' }'
698717
args = []
@@ -702,7 +721,8 @@ def all_asm_consts(metadata):
702721
const = 'function(' + ', '.join(args) + ') ' + const
703722
asm_consts[int(k)] = const
704723
all_sigs += sigs
705-
return asm_consts, all_sigs
724+
if call_types: all_call_types += call_types
725+
return asm_consts, all_sigs, all_call_types
706726

707727

708728
def unfloat(s):

site/source/docs/api_reference/Filesystem-API.rst

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -312,7 +312,7 @@ File system API
312312
#include <emscripten.h>
313313
314314
int main() {
315-
EM_ASM(
315+
MAIN_THREAD_EM_ASM(
316316
FS.writeFile('file', 'foobar');
317317
FS.symlink('file', 'link');
318318
console.log(FS.readlink('link'));
@@ -341,7 +341,7 @@ File system API
341341
#include <emscripten.h>
342342
343343
int main() {
344-
EM_ASM(
344+
MAIN_THREAD_EM_ASM(
345345
FS.writeFile('file', 'foobar');
346346
console.log(FS.stat('file'));
347347
);
@@ -459,7 +459,7 @@ File system API
459459
#include <emscripten.h>
460460
461461
int main() {
462-
EM_ASM(
462+
MAIN_THREAD_EM_ASM(
463463
FS.writeFile('file', 'foobar');
464464
FS.truncate('file', 3);
465465
console.log(FS.readFile('file', { encoding: 'utf8' }));

src/library.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4372,6 +4372,9 @@ LibraryManager.library = {
43724372
emscripten_asm_const: true,
43734373
emscripten_asm_const_int: true,
43744374
emscripten_asm_const_double: true,
4375+
emscripten_asm_const_int_sync_on_main_thread: true,
4376+
emscripten_asm_const_double_sync_on_main_thread: true,
4377+
emscripten_asm_const_async_on_main_thread: true,
43754378

43764379
// ======== compiled code from system/lib/compiler-rt , see readme therein
43774380
__muldsi3__asm: true,

src/settings.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -795,6 +795,8 @@ var PTHREAD_HINT_NUM_CORES = 4;
795795

796796
var PTHREADS_PROFILING = 0; // True when building with --threadprofiler
797797

798+
var PTHREADS_DEBUG = 0; // If true, add in debug traces for diagnosing pthreads related issues.
799+
798800
var MAX_GLOBAL_ALIGN = -1; // received from the backend
799801

800802
// Duplicate function elimination. This coalesces function bodies that are

system/include/emscripten/em_asm.h

Lines changed: 33 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,12 @@ extern "C" {
3232
void emscripten_asm_const(const char* code);
3333
int emscripten_asm_const_int(const char* code, ...);
3434
double emscripten_asm_const_double(const char* code, ...);
35+
36+
int emscripten_asm_const_int_sync_on_main_thread(const char* code, ...);
37+
double emscripten_asm_const_double_sync_on_main_thread(const char* code, ...);
38+
39+
void emscripten_asm_const_async_on_main_thread(const char* code, ...);
40+
3541
#ifdef __cplusplus
3642
}
3743
#endif
@@ -43,15 +49,39 @@ void emscripten_asm_const(const char* code);
4349
// then wrap the whole code block inside parentheses (). See tests/core/test_em_asm_2.cpp
4450
// for example code snippets.
4551

46-
// Runs the given JavaScript code, and returns nothing back.
52+
// Runs the given JavaScript code on the calling thread (synchronously), and returns no value back.
4753
#define EM_ASM(code, ...) ((void)emscripten_asm_const_int(#code, ##__VA_ARGS__))
4854

49-
// Runs the given JavaScript code, and returns an integer back.
55+
// Runs the given JavaScript code on the calling thread (synchronously), and returns an integer back.
5056
#define EM_ASM_INT(code, ...) emscripten_asm_const_int(#code, ##__VA_ARGS__)
5157

52-
// Runs the given JavaScript code, and returns a double back.
58+
// Runs the given JavaScript code on the calling thread (synchronously), and returns a double back.
5359
#define EM_ASM_DOUBLE(code, ...) emscripten_asm_const_double(#code, ##__VA_ARGS__)
5460

61+
// Runs the given JavaScript code synchronously on the main browser thread, and returns no value back.
62+
// Call this function for example to access DOM elements in a pthread/web worker. Avoid calling this
63+
// function in performance sensitive code, because this will effectively sleep the calling thread until the
64+
// main browser thread is able to service the proxied function call. If you have multiple MAIN_THREAD_EM_ASM()
65+
// code blocks to call in succession, it will likely be much faster to coalesce all the calls to a single
66+
// MAIN_THREAD_EM_ASM() block. If you do not need synchronization nor a return value back, consider using
67+
// the function MAIN_THREAD_ASYNC_EM_ASM() instead, which will not block.
68+
#define MAIN_THREAD_EM_ASM(code, ...) ((void)emscripten_asm_const_int_sync_on_main_thread(#code, ##__VA_ARGS__))
69+
70+
// Runs the given JavaScript code synchronously on the main browser thread, and returns an integer back.
71+
// The same considerations apply as with MAIN_THREAD_EM_ASM().
72+
#define MAIN_THREAD_EM_ASM_INT(code, ...) emscripten_asm_const_int_sync_on_main_thread(#code, ##__VA_ARGS__)
73+
74+
// Runs the given JavaScript code synchronously on the main browser thread, and returns a double back.
75+
// The same considerations apply as with MAIN_THREAD_EM_ASM().
76+
#define MAIN_THREAD_EM_ASM_DOUBLE(code, ...) emscripten_asm_const_double_sync_on_main_thread(#code, ##__VA_ARGS__)
77+
78+
// Asynchronously dispatches the given JavaScript code to be run on the main browser thread.
79+
// If the calling thread is the main browser thread, then the specified JavaScript code is executed
80+
// synchronously. Otherwise an event will be queued on the main browser thread to execute the call
81+
// later (think postMessage()), and this call will immediately return without waiting. Be sure to
82+
// guard any accesses to shared memory on the heap inside the JavaScript code with appropriate locking.
83+
#define MAIN_THREAD_ASYNC_EM_ASM(code, ...) ((void)emscripten_asm_const_async_on_main_thread(#code, ##__VA_ARGS__))
84+
5585
// Old forms for compatibility, no need to use these.
5686
// Replace EM_ASM_, EM_ASM_ARGS and EM_ASM_INT_V with EM_ASM_INT,
5787
// and EM_ASM_DOUBLE_V with EM_ASM_DOUBLE.
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
#include <emscripten.h>
2+
#include <stdio.h>
3+
4+
int main()
5+
{
6+
// Test that on main browser thread, MAIN_THREAD_ASYNC_EM_ASM() will get
7+
// synchronously executed.
8+
printf("Before MAIN_THREAD_ASYNC_EM_ASM\n");
9+
MAIN_THREAD_ASYNC_EM_ASM(Module.print('Inside MAIN_THREAD_ASYNC_EM_ASM: ' + $0 + ' ' + $1), 42, 3.5);
10+
printf("After MAIN_THREAD_ASYNC_EM_ASM\n");
11+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Before MAIN_THREAD_ASYNC_EM_ASM
2+
Inside MAIN_THREAD_ASYNC_EM_ASM: 42 3.5
3+
After MAIN_THREAD_ASYNC_EM_ASM

tests/test_core.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1603,10 +1603,32 @@ def test_em_asm(self):
16031603
self.do_run_in_out_file_test('tests', 'core', 'test_em_asm')
16041604
self.do_run_in_out_file_test('tests', 'core', 'test_em_asm', force_c=True)
16051605

1606+
# Tests various different ways to invoke the EM_ASM(), EM_ASM_INT() and EM_ASM_DOUBLE() macros.
16061607
def test_em_asm_2(self):
16071608
self.do_run_in_out_file_test('tests', 'core', 'test_em_asm_2')
16081609
self.do_run_in_out_file_test('tests', 'core', 'test_em_asm_2', force_c=True)
16091610

1611+
# Tests various different ways to invoke the MAIN_THREAD_EM_ASM(), MAIN_THREAD_EM_ASM_INT() and MAIN_THREAD_EM_ASM_DOUBLE() macros.
1612+
# 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
1613+
# test cases are added to test_em_asm_2.cpp for EM_ASM, they will also get tested in MAIN_THREAD_EM_ASM form.
1614+
@@no_wasm_backend
1615+
def test_main_thread_em_asm(self):
1616+
src = open(path_from_root('tests', 'core', 'test_em_asm_2.cpp'), 'r').read()
1617+
test_file = 'src.cpp'
1618+
open(test_file, 'w').write(src.replace('EM_ASM', 'MAIN_THREAD_EM_ASM'))
1619+
1620+
expected_result = open(path_from_root('tests', 'core', 'test_em_asm_2.out'), 'r').read()
1621+
expected_result_file = 'result.out'
1622+
open(expected_result_file, 'w').write(expected_result.replace('EM_ASM', 'MAIN_THREAD_EM_ASM'))
1623+
1624+
self.do_run_from_file(test_file, expected_result_file)
1625+
self.do_run_from_file(test_file, expected_result_file, force_c=True)
1626+
1627+
@no_wasm_backend
1628+
def test_main_thread_async_em_asm(self):
1629+
self.do_run_in_out_file_test('tests', 'core', 'test_main_thread_async_em_asm')
1630+
self.do_run_in_out_file_test('tests', 'core', 'test_main_thread_async_em_asm', force_c=True)
1631+
16101632
def test_em_asm_unicode(self):
16111633
self.do_run(r'''
16121634
#include <emscripten.h>

0 commit comments

Comments
 (0)