From 0f55bf4bc107f1956c8b2144d5ffebf09c9f0f76 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jukka=20Jyl=C3=A4nki?= Date: Mon, 7 Feb 2022 15:41:29 +0200 Subject: [PATCH 01/65] Add Wasm Workers Add Wasm Workers --- emcc.py | 69 +++- src/jsifier.js | 8 + src/library.js | 5 +- src/library_exceptions.js | 4 +- src/library_wasm_worker.js | 371 ++++++++++++++++++ src/postamble_minimal.js | 4 + src/preamble_minimal.js | 13 +- src/runtime_strings.js | 6 +- src/runtime_strings_extra.js | 4 +- src/settings.js | 6 + src/settings_internal.js | 6 + src/shell_minimal.js | 9 +- src/wasm2js.js | 4 +- src/wasm_worker.js | 21 + system/include/emscripten/wasm_worker.h | 299 ++++++++++++++ system/lib/wasm_worker/library_wasm_worker.c | 186 +++++++++ tests/report_result.h | 2 +- tests/test_browser.py | 140 +++++++ tests/wasm_worker/cancel_all_wait_asyncs.c | 80 ++++ .../cancel_all_wait_asyncs_at_address.c | 84 ++++ tests/wasm_worker/cancel_wait_async.c | 72 ++++ .../hardware_concurrency_is_lock_free.c | 35 ++ tests/wasm_worker/hello_wasm_worker.c | 27 ++ tests/wasm_worker/lock_async_acquire.c | 108 +++++ .../wasm_worker/lock_busyspin_wait_acquire.c | 50 +++ .../lock_busyspin_waitinf_acquire.c | 42 ++ tests/wasm_worker/lock_wait_acquire.c | 47 +++ tests/wasm_worker/lock_wait_acquire2.c | 57 +++ tests/wasm_worker/lock_waitinf_acquire.c | 75 ++++ tests/wasm_worker/no_proxied_js_functions.c | 44 +++ tests/wasm_worker/no_proxied_js_functions.js | 5 + tests/wasm_worker/post_function.c | 107 +++++ .../post_function_to_main_thread.c | 36 ++ tests/wasm_worker/semaphore_try_acquire.c | 41 ++ tests/wasm_worker/semaphore_waitinf_acquire.c | 91 +++++ .../wasm_worker/terminate_all_wasm_workers.c | 88 +++++ tests/wasm_worker/terminate_wasm_worker.c | 84 ++++ tests/wasm_worker/wait32_notify.c | 76 ++++ tests/wasm_worker/wait64_notify.c | 76 ++++ tests/wasm_worker/wait_async.c | 87 ++++ tests/wasm_worker/wasm_worker_self_id.c | 52 +++ tests/wasm_worker/wasm_worker_sleep.c | 24 ++ tools/building.py | 2 +- tools/minimal_runtime_shell.py | 34 +- tools/settings.py | 2 + tools/system_libs.py | 28 +- 46 files changed, 2675 insertions(+), 36 deletions(-) create mode 100644 src/library_wasm_worker.js create mode 100644 src/wasm_worker.js create mode 100644 system/include/emscripten/wasm_worker.h create mode 100644 system/lib/wasm_worker/library_wasm_worker.c create mode 100644 tests/wasm_worker/cancel_all_wait_asyncs.c create mode 100644 tests/wasm_worker/cancel_all_wait_asyncs_at_address.c create mode 100644 tests/wasm_worker/cancel_wait_async.c create mode 100644 tests/wasm_worker/hardware_concurrency_is_lock_free.c create mode 100644 tests/wasm_worker/hello_wasm_worker.c create mode 100644 tests/wasm_worker/lock_async_acquire.c create mode 100644 tests/wasm_worker/lock_busyspin_wait_acquire.c create mode 100644 tests/wasm_worker/lock_busyspin_waitinf_acquire.c create mode 100644 tests/wasm_worker/lock_wait_acquire.c create mode 100644 tests/wasm_worker/lock_wait_acquire2.c create mode 100644 tests/wasm_worker/lock_waitinf_acquire.c create mode 100644 tests/wasm_worker/no_proxied_js_functions.c create mode 100644 tests/wasm_worker/no_proxied_js_functions.js create mode 100644 tests/wasm_worker/post_function.c create mode 100644 tests/wasm_worker/post_function_to_main_thread.c create mode 100644 tests/wasm_worker/semaphore_try_acquire.c create mode 100644 tests/wasm_worker/semaphore_waitinf_acquire.c create mode 100644 tests/wasm_worker/terminate_all_wasm_workers.c create mode 100644 tests/wasm_worker/terminate_wasm_worker.c create mode 100644 tests/wasm_worker/wait32_notify.c create mode 100644 tests/wasm_worker/wait64_notify.c create mode 100644 tests/wasm_worker/wait_async.c create mode 100644 tests/wasm_worker/wasm_worker_self_id.c create mode 100644 tests/wasm_worker/wasm_worker_sleep.c diff --git a/emcc.py b/emcc.py index fa3076eb8e0c..4afcbb9d6fc0 100755 --- a/emcc.py +++ b/emcc.py @@ -308,7 +308,7 @@ def setup_environment_settings(): if not settings.ENVIRONMENT_MAY_BE_WORKER and settings.PROXY_TO_WORKER: exit_with_error('If you specify --proxy-to-worker and specify a "-s ENVIRONMENT=" directive, it must include "worker" as a target! (Try e.g. -s ENVIRONMENT=web,worker)') - if not settings.ENVIRONMENT_MAY_BE_WORKER and settings.USE_PTHREADS: + if not settings.ENVIRONMENT_MAY_BE_WORKER and (settings.USE_PTHREADS or settings.WASM_WORKERS): exit_with_error('When building with multithreading enabled and a "-s ENVIRONMENT=" directive is specified, it must include "worker" as a target! (Try e.g. -s ENVIRONMENT=web,worker)') @@ -830,6 +830,12 @@ def get_cflags(user_args): if settings.EMSCRIPTEN_TRACING: cflags.append('-D__EMSCRIPTEN_TRACING__=1') + if settings.SHARED_MEMORY: + cflags.append('-D__EMSCRIPTEN_SHARED_MEMORY__=1') + + if settings.WASM_WORKERS: + cflags.append('-D__EMSCRIPTEN_WASM_WORKERS__=1') + if not settings.STRICT: # The preprocessor define EMSCRIPTEN is deprecated. Don't pass it to code # in strict mode. Code should use the define __EMSCRIPTEN__ instead. @@ -1432,7 +1438,7 @@ def phase_setup(options, state, newargs, user_settings): if settings.MAIN_MODULE or settings.SIDE_MODULE: settings.RELOCATABLE = 1 - if settings.USE_PTHREADS and '-pthread' not in newargs: + if (settings.USE_PTHREADS or settings.WASM_WORKERS) and '-pthread' not in newargs: newargs += ['-pthread'] if 'DISABLE_EXCEPTION_CATCHING' in user_settings and 'EXCEPTION_CATCHING_ALLOWED' in user_settings: @@ -1592,6 +1598,10 @@ def phase_linker_setup(options, state, newargs, user_settings): # Requesting both Wasm and Wasm2JS support settings.WASM2JS = 1 + # Pthreads and Wasm Workers require targeting shared Wasm memory (SAB). + if settings.USE_PTHREADS or settings.WASM_WORKERS: + settings.SHARED_MEMORY = 1 + if (options.oformat == OFormat.WASM or settings.PURE_WASI) and not settings.SIDE_MODULE: # if the output is just a wasm file, it will normally be a standalone one, # as there is no JS. an exception are side modules, as we can't tell at @@ -1687,7 +1697,7 @@ def phase_linker_setup(options, state, newargs, user_settings): # https://github.com/whatwg/encoding/issues/172 # When supporting shell environments, do not do this as TextDecoder is not # widely supported there. - if settings.SHRINK_LEVEL >= 2 and not settings.USE_PTHREADS and \ + if settings.SHRINK_LEVEL >= 2 and not settings.SHARED_MEMORY and \ not settings.ENVIRONMENT_MAY_BE_SHELL: default_setting(user_settings, 'TEXTDECODER', 2) @@ -1758,7 +1768,7 @@ def phase_linker_setup(options, state, newargs, user_settings): '$mergeLibSymbols', ] - if settings.USE_PTHREADS: + if settings.USE_PTHREADS: # or settings.WASM_WORKERS: settings.DEFAULT_LIBRARY_FUNCS_TO_INCLUDE += [ '$registerTlsInit', ] @@ -2028,6 +2038,10 @@ def phase_linker_setup(options, state, newargs, user_settings): # overrides that. default_setting(user_settings, 'ABORTING_MALLOC', 0) + if settings.SHARED_MEMORY: + # UTF8Decoder.decode doesn't work with a view of a SharedArrayBuffer + settings.TEXTDECODER = 0 + if settings.USE_PTHREADS: if settings.USE_PTHREADS == 2: exit_with_error('USE_PTHREADS=2 is no longer supported') @@ -2077,11 +2091,20 @@ def phase_linker_setup(options, state, newargs, user_settings): else: settings.JS_LIBRARIES.append((0, 'library_pthread_stub.js')) + # TODO: Move this into the library JS file once it becomes possible. # See https://github.com/emscripten-core/emscripten/pull/15982 if settings.INCLUDE_FULL_LIBRARY and not settings.DISABLE_EXCEPTION_CATCHING: settings.EXPORTED_FUNCTIONS += ['_emscripten_format_exception', '_free'] + if settings.WASM_WORKERS: + settings.EXPORTED_FUNCTIONS += ['_emscripten_stack_set_limits'] + settings.DEFAULT_LIBRARY_FUNCS_TO_INCLUDE += ['_wasm_worker_initializeRuntime'] + # set location of Wasm Worker bootstrap .js + if settings.WASM_WORKERS == 1: + settings.WASM_WORKER_FILE = unsuffixed(os.path.basename(target)) + '.ww.js' + settings.JS_LIBRARIES.append((0, shared.path_from_root('src', 'library_wasm_worker.js'))) + if settings.FORCE_FILESYSTEM and not settings.MINIMAL_RUNTIME: # when the filesystem is forced, we export by default methods that filesystem usage # may need, including filesystem usage from standalone file packager output (i.e. @@ -2152,6 +2175,20 @@ def include_and_export(name): elif settings.PROXY_TO_PTHREAD: exit_with_error('-s PROXY_TO_PTHREAD=1 requires -s USE_PTHREADS to work!') + if settings.WASM_WORKERS: + if settings.SINGLE_FILE: + exit_with_error('TODO: -s SINGLE_FILE=1 is currently not supported with -s WASM_WORKERS!') + if settings.LINKABLE: + exit_with_error('TODO: -s LINKABLE=1 is currently not supported with -s WASM_WORKERS!') + if settings.SIDE_MODULE: + exit_with_error('TODO: -s SIDE_MODULE=1 is currently not supported with -s WASM_WORKERS!') + if settings.MAIN_MODULE: + exit_with_error('TODO: -s MAIN_MODULE=1 is currently not supported with -s WASM_WORKERS!') + if settings.PROXY_TO_WORKER: + exit_with_error('--proxy-to-worker is not supported with -s WASM_WORKERS!') + if settings.USE_PTHREADS: + exit_with_error('TODO: -pthread and -s USE_PTHREADS=1 are currently not supported with -s WASM_WORKERS!') + def check_memory_setting(setting): if settings[setting] % webassembly.WASM_PAGE_SIZE != 0: exit_with_error(f'{setting} must be a multiple of WebAssembly page size (64KiB), was {settings[setting]}') @@ -2222,8 +2259,8 @@ def check_memory_setting(setting): exit_with_error(f'Due to collision in variable name "Module", the shell file "{options.shell_path}" is not compatible with build options "-s MODULARIZE=1 -s EXPORT_NAME=Module". Either provide your own shell file, change the name of the export to something else to avoid the name collision. (see https://github.com/emscripten-core/emscripten/issues/7950 for details)') if settings.STANDALONE_WASM: - if settings.USE_PTHREADS: - exit_with_error('STANDALONE_WASM does not support pthreads yet') + if settings.SHARED_MEMORY: + exit_with_error('STANDALONE_WASM does not support shared memories yet') if settings.MINIMAL_RUNTIME: exit_with_error('MINIMAL_RUNTIME reduces JS size, and is incompatible with STANDALONE_WASM which focuses on ignoring JS anyhow and being 100% wasm') # the wasm must be runnable without the JS, so there cannot be anything that @@ -2231,7 +2268,7 @@ def check_memory_setting(setting): settings.LEGALIZE_JS_FFI = 0 # TODO(sbc): Remove WASM2JS here once the size regression it would introduce has been fixed. - if settings.USE_PTHREADS or settings.RELOCATABLE or settings.ASYNCIFY_LAZY_LOAD_CODE or settings.WASM2JS: + if settings.SHARED_MEMORY or settings.RELOCATABLE or settings.ASYNCIFY_LAZY_LOAD_CODE or settings.WASM2JS: settings.IMPORTED_MEMORY = 1 if settings.WASM_BIGINT: @@ -2266,9 +2303,9 @@ def check_memory_setting(setting): # can use a .mem file like asm.js used to. # generally we follow what the options tell us to do (which is to use # a .mem file in most cases, since it is binary & compact). however, for - # pthreads we must keep the memory segments in the wasm as they will be - # passive segments which the .mem format cannot handle. - settings.MEM_INIT_IN_WASM = not options.memory_init_file or settings.SINGLE_FILE or settings.USE_PTHREADS + # shared memory builds we must keep the memory segments in the wasm as + # they will be passive segments which the .mem format cannot handle. + settings.MEM_INIT_IN_WASM = not options.memory_init_file or settings.SINGLE_FILE or settings.USE_SHARED_MEMORY else: # wasm includes the mem init in the wasm binary. The exception is # wasm2js, which behaves more like js. @@ -2830,8 +2867,8 @@ def phase_final_emitting(options, state, target, wasm_target, memfile): # src = re.sub(r'\n+[ \n]*\n+', '\n', src) # write_file(final_js, src) + target_dir = os.path.dirname(os.path.abspath(target)) if settings.USE_PTHREADS: - target_dir = os.path.dirname(os.path.abspath(target)) worker_output = os.path.join(target_dir, settings.PTHREAD_WORKER_FILE) contents = shared.read_and_preprocess(utils.path_from_root('src/worker.js'), expand_macros=True) write_file(worker_output, contents) @@ -2841,6 +2878,16 @@ def phase_final_emitting(options, state, target, wasm_target, memfile): minified_worker = building.acorn_optimizer(worker_output, ['minifyWhitespace'], return_output=True) write_file(worker_output, minified_worker) + if settings.WASM_WORKERS == 1: + worker_output = os.path.join(target_dir, settings.WASM_WORKER_FILE) + with open(worker_output, 'w') as f: + f.write(shared.read_and_preprocess(shared.path_from_root('src', 'wasm_worker.js'), expand_macros=True)) + + # Minify the wasm_worker.js file in optimized builds + if (settings.OPT_LEVEL >= 1 or settings.SHRINK_LEVEL >= 1) and not settings.DEBUG_LEVEL: + minified_worker = building.acorn_optimizer(worker_output, ['minifyWhitespace'], return_output=True) + open(worker_output, 'w').write(minified_worker) + # track files that will need native eols generated_text_files_with_native_eols = [] diff --git a/src/jsifier.js b/src/jsifier.js index eaf1ffdab71e..a4490d52f19a 100644 --- a/src/jsifier.js +++ b/src/jsifier.js @@ -292,6 +292,14 @@ function ${name}(${args}) { ${body} }\n`); proxiedFunctionTable.push(finalName); + } else if (WASM_WORKERS && ASSERTIONS && proxyingMode) { + // In ASSERTIONS builds add runtime checks that proxied functions are not attempted to be called in Wasm Workers + // (since there is no automatic proxying architecture available) + contentText = modifyFunction(snippet, function(name, args, body) { + return 'function ' + name + '(' + args + ') {\n' + + 'assert(!ENVIRONMENT_IS_WASM_WORKER, "Attempted to call proxied function \\"' + name + '\\" in a Wasm Worker, but in Wasm Worker enabled builds, proxied function architecture is not available!");\n' + + body + '}\n'; + }); } else if ((USE_ASAN || USE_LSAN || UBSAN_RUNTIME) && LibraryManager.library[ident + '__noleakcheck']) { contentText = modifyFunction(snippet, (name, args, body) => ` function ${name}(${args}) { diff --git a/src/library.js b/src/library.js index b1947fc69d75..caa19b0a71fb 100644 --- a/src/library.js +++ b/src/library.js @@ -163,9 +163,9 @@ LibraryManager.library = { return false; // malloc will report failure #endif // ABORTING_MALLOC #else // ALLOW_MEMORY_GROWTH == 0 - // With pthreads, races can happen (another thread might increase the size + // With multithreaded builds, races can happen (another thread might increase the size // in between), so return a failure, and let the caller retry. -#if USE_PTHREADS +#if SHARED_MEMORY if (requestedSize <= oldSize) { return false; } @@ -2416,6 +2416,7 @@ LibraryManager.library = { #endif #if USE_PTHREADS // Pthreads need their clocks synchronized to the execution of the main thread, so give them a special form of the function. +// N.b. Wasm workers do not provide this kind of clock synchronization. "if (ENVIRONMENT_IS_PTHREAD) {\n" + " _emscripten_get_now = () => performance.now() - Module['__performance_now_clock_drift'];\n" + "} else " + diff --git a/src/library_exceptions.js b/src/library_exceptions.js index a8a85b3737ff..6d1a1a3ba013 100644 --- a/src/library_exceptions.js +++ b/src/library_exceptions.js @@ -74,7 +74,7 @@ var LibraryExceptions = { } this.add_ref = function() { -#if USE_PTHREADS +#if SHARED_MEMORY Atomics.add(HEAP32, (this.ptr + {{{ C_STRUCTS.__cxa_exception.referenceCount }}}) >> 2, 1); #else var value = {{{ makeGetValue('this.ptr', C_STRUCTS.__cxa_exception.referenceCount, 'i32') }}}; @@ -84,7 +84,7 @@ var LibraryExceptions = { // Returns true if last reference released. this.release_ref = function() { -#if USE_PTHREADS +#if SHARED_MEMORY var prev = Atomics.sub(HEAP32, (this.ptr + {{{ C_STRUCTS.__cxa_exception.referenceCount }}}) >> 2, 1); #else var prev = {{{ makeGetValue('this.ptr', C_STRUCTS.__cxa_exception.referenceCount, 'i32') }}}; diff --git a/src/library_wasm_worker.js b/src/library_wasm_worker.js new file mode 100644 index 000000000000..7162251af06e --- /dev/null +++ b/src/library_wasm_worker.js @@ -0,0 +1,371 @@ +{{{ (function() { global.captureModuleArg = function() { return MODULARIZE ? '' : 'self.Module=d;'; }; return null; })(); }}} +{{{ (function() { global.instantiateModule = function() { return MODULARIZE ? `${EXPORT_NAME}(d);` : ''; }; return null; })(); }}} + +mergeInto(LibraryManager.library, { + wasm_workers: {}, + wasm_workers_id: 1, + + // Starting up a Wasm Worker is an asynchronous operation, hence if the parent thread performs any + // postMessage()-based wasm function calls s to the Worker, they must be delayed until the async + // startup has finished, after which these postponed function calls can be dispatched. + _wasm_worker_delayedMessageQueue: [], + + _wasm_worker_appendToQueue: function(e) { + __wasm_worker_delayedMessageQueue.push(e); + }, + + // Executes a wasm function call received via a postMessage. + _wasm_worker_runPostMessage: function(e) { + var data = e.data, wasmCall = data['_wsc']; + if (wasmCall) { + var func = wasmTable.get(wasmCall); + if (data['a'] === 0) func(); + else if (data['a'] === 1) func(data['x']); + else if (data['a'] === 2) func(data['x'], data['y']); + else if (data['a'] === 3) func(data['x'], data['y'], data['z']); + else func.apply(null, data['x']); + } + }, + + // src/postamble_minimal.js brings this symbol in to the build, and calls this function. + _wasm_worker_initializeRuntime__deps: ['_wasm_worker_delayedMessageQueue', '_wasm_worker_runPostMessage'], + _wasm_worker_initializeRuntime: function() { + // Establish the stack space for this Wasm Worker: + var stackTop = Module["sb"] + Module["sz"]; +#if ASSERTIONS + assert(stackTop % 16 == 0); + assert(Module["sb"] % 16 == 0); +#endif + // TODO: Fuse these to the same function "emscripten_establish_stack". + _emscripten_stack_set_limits(stackTop, Module["sb"]); + stackRestore(stackTop); +#if STACK_OVERFLOW_CHECK >= 2 + ___set_stack_limits(_emscripten_stack_get_base(), _emscripten_stack_get_end()); +#endif + + // The Wasm Worker runtime is now up, so we can start processing + // any postMessage function calls that have been received. Drop the temp + // message handler that appended incoming postMessage function calls to a queue ... + removeEventListener('message', __wasm_worker_appendToQueue); + // ... then flush whatever messages we may have gotten in the queue ... + __wasm_worker_delayedMessageQueue.forEach(__wasm_worker_runPostMessage); + __wasm_worker_delayedMessageQueue = null; + // ... and finally register the proper postMessage handler that immediately + // dispatches incoming function calls without queueing them. + addEventListener('message', __wasm_worker_runPostMessage); + }, + +#if WASM_WORKERS == 2 + // In WASM_WORKERS == 2 build mode, we create the Wasm Worker global scope script from a string bundled in the main application JS file. This simplifies the number of deployed JS files with the app, + // but has a downside that the generated build output will no longer be csp-eval compliant. https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/script-src#unsafe_eval_expressions + _wasmWorkerBlobUrl: "URL.createObjectURL(new Blob(['onmessage=function(d){onmessage=null;d=d.data;{{{ captureModuleArg() }}}importScripts(d.js);{{{ instantiateModule() }}}d.wasm=d.mem=d.js=0;}'],{type:'application/javascript'}))", +#endif + + _emscripten_create_wasm_worker__deps: ['wasm_workers', 'wasm_workers_id', '_wasm_worker_appendToQueue', '_wasm_worker_runPostMessage' +#if WASM_WORKERS == 2 + , '_wasmWorkerBlobUrl' +#endif + ], + _emscripten_create_wasm_worker__postset: 'if (ENVIRONMENT_IS_WASM_WORKER) {\n' + + '_wasm_workers[0] = this;\n' + + 'addEventListener("message", __wasm_worker_appendToQueue);\n' + + '}\n', + _emscripten_create_wasm_worker: function(stackLowestAddress, stackSize) { +#if ASSERTIONS + assert(stackLowestAddress % 16 == 0); + assert(stackSize % 16 == 0); +#endif + var worker = _wasm_workers[_wasm_workers_id] = new Worker( +#if WASM_WORKERS == 2 + __wasmWorkerBlobUrl +#else + Module['wasmWorker'] +#endif + ); + // Craft the Module object for the Wasm Worker scope: + worker.postMessage({ + '$ww': _wasm_workers_id, // Signal with a non-zero value that this Worker will be a Wasm Worker, and not the main browser thread. + 'wasm': Module['wasm'], + 'js': Module['js'], + 'mem': wasmMemory, + 'sb': stackLowestAddress, + 'sz': stackSize, + }); + worker.addEventListener('message', __wasm_worker_runPostMessage); + return _wasm_workers_id++; + }, + + emscripten_terminate_wasm_worker: function(id) { + if (_wasm_workers[id]) { + _wasm_workers[id].terminate(); + delete _wasm_workers[id]; + } + }, + + emscripten_terminate_all_wasm_workers: function() { + Object.values(_wasm_workers).forEach((worker) => { + worker.terminate(); + }); + _wasm_workers = {}; + }, + + emscripten_current_thread_is_wasm_worker: function() { +#if WASM_WORKERS + return ENVIRONMENT_IS_WASM_WORKER; +#else + // implicit return 0; +#endif + }, + + emscripten_wasm_worker_self_id: function() { + return Module['$ww']; + }, + + emscripten_wasm_worker_post_function_v__deps: ['$dynCall'], + emscripten_wasm_worker_post_function_v__sig: 'vii', + emscripten_wasm_worker_post_function_v: function(id, funcPtr) { + var worker = _wasm_workers[id]; + worker.postMessage({ + '_wsc': funcPtr, // "WaSm Call" + 'a': 0, + }); + }, + + emscripten_wasm_worker_post_function_1__deps: ['$dynCall'], + emscripten_wasm_worker_post_function_1__sig: 'viid', + emscripten_wasm_worker_post_function_1: function(id, funcPtr, arg0) { + var worker = _wasm_workers[id]; + worker.postMessage({ + '_wsc': funcPtr, // "WaSm Call" + 'a': 1, + 'x': arg0 + }); + }, + + emscripten_wasm_worker_post_function_vi: 'emscripten_wasm_worker_post_function_1', + emscripten_wasm_worker_post_function_vd: 'emscripten_wasm_worker_post_function_1', + + emscripten_wasm_worker_post_function_2__deps: ['$dynCall'], + emscripten_wasm_worker_post_function_2__sig: 'viidd', + emscripten_wasm_worker_post_function_2: function(id, funcPtr, arg0, arg1) { + var worker = _wasm_workers[id]; + worker.postMessage({ + '_wsc': funcPtr, // "WaSm Call" + 'a': 2, + 'x': arg0, + 'y': arg1 + }); + }, + emscripten_wasm_worker_post_function_vii: 'emscripten_wasm_worker_post_function_2', + emscripten_wasm_worker_post_function_vdd: 'emscripten_wasm_worker_post_function_2', + + emscripten_wasm_worker_post_function_3__deps: ['$dynCall'], + emscripten_wasm_worker_post_function_3__sig: 'viiddd', + emscripten_wasm_worker_post_function_3: function(id, funcPtr, arg0, arg1, arg2) { + var worker = _wasm_workers[id]; + worker.postMessage({ + '_wsc': funcPtr, // "WaSm Call" + 'a': 3, + 'x': arg0, + 'y': arg1, + 'z': arg2 + }); + }, + emscripten_wasm_worker_post_function_viii: 'emscripten_wasm_worker_post_function_3', + emscripten_wasm_worker_post_function_vddd: 'emscripten_wasm_worker_post_function_3', + + emscripten_wasm_worker_post_function_sig__deps: ['$readAsmConstArgs'], + emscripten_wasm_worker_post_function_sig: function(id, funcPtr, sigPtr, varargs) { +#if ASSERTIONS + assert(id >= 0); + assert(funcPtr); + assert(sigPtr); + assert(UTF8ToString(sigPtr)[0] != 'v', 'Do NOT specify the return argument in the signature string for a call to emscripten_wasm_worker_post_function_sig(), it is always discarded.'); + assert(varargs); +#endif + var worker = _wasm_workers[id]; + worker.postMessage({ + '_wsc': funcPtr, // "WaSm Call" + 'a': -1, + 'x': readAsmConstArgs(sigPtr, varargs) + }); + }, + + _emscripten_atomic_wait_states: "['ok', 'not-equal', 'timed-out']", + +// Chrome 87 (and hence Edge 87) shipped Atomics.waitAsync (https://www.chromestatus.com/feature/6243382101803008) +// However its implementation is faulty: https://bugs.chromium.org/p/chromium/issues/detail?id=1167541 +// Firefox Nightly 86.0a1 (2021-01-15) does not yet have it, https://bugzilla.mozilla.org/show_bug.cgi?id=1467846 +// And at the time of writing, no other browser has it either. +#if MIN_EDGE_VERSION != TARGET_NOT_SUPPORTED || MIN_CHROME_VERSION != TARGET_NOT_SUPPORTED || MIN_SAFARI_VERSION != TARGET_NOT_SUPPORTED || MIN_FIREFOX_VERSION != TARGET_NOT_SUPPORTED || ENVIRONMENT_MAY_BE_NODE + // Partially polyfill Atomics.waitAsync() if not available in the browser. + // Also always polyfill in Chrome-based browsers, where Atomics.waitAsync is broken, + // see https://bugs.chromium.org/p/chromium/issues/detail?id=1167541 + // https://github.com/tc39/proposal-atomics-wait-async/blob/master/PROPOSAL.md + // This polyfill performs polling with setTimeout() to observe a change in the target memory location. + emscripten_atomic_wait_async__postset: "if (!Atomics['waitAsync'] || navigator.userAgent.indexOf('Chrome') != -1) { \n"+ +"var __Atomics_waitAsyncAddresses = [/*[i32a, index, value, maxWaitMilliseconds, promiseResolve]*/];\n"+ +"function __Atomics_pollWaitAsyncAddresses() {\n"+ +" var now = performance.now();\n"+ +" var l = __Atomics_waitAsyncAddresses.length;\n"+ +" for(var i = 0; i < l; ++i) {\n"+ +" var a = __Atomics_waitAsyncAddresses[i];\n"+ +" var expired = (now > a[3]);\n"+ +" var awoken = (Atomics.load(a[0], a[1]) != a[2]);\n"+ +" if (expired || awoken) {\n"+ +" __Atomics_waitAsyncAddresses[i--] = __Atomics_waitAsyncAddresses[--l];\n"+ +" __Atomics_waitAsyncAddresses.length = l;\n"+ +" a[4](awoken ? 'ok': 'timed-out');\n"+ +" }\n"+ +" }\n"+ +" if (l) {\n"+ +" // If we still have addresses to wait, loop the timeout handler to continue polling.\n"+ +" setTimeout(__Atomics_pollWaitAsyncAddresses, 10);\n"+ +" }\n"+ +"}\n"+ +#if ASSERTIONS +" if (!ENVIRONMENT_IS_WASM_WORKER) console.error('Current environment does not support Atomics.waitAsync(): polyfilling it, but this is going to be suboptimal.');\n"+ +#endif +"Atomics['waitAsync'] = function(i32a, index, value, maxWaitMilliseconds) {\n"+ +" var val = Atomics.load(i32a, index);\n"+ +" if (val != value) return { async: false, value: 'not-equal' };\n"+ +" if (maxWaitMilliseconds <= 0) return { async: false, value: 'timed-out' };\n"+ +" var maxWaitMilliseconds = performance.now() + (maxWaitMilliseconds || Infinity);\n"+ +" var promiseResolve;\n"+ +" var promise = new Promise((resolve) => { promiseResolve = resolve; });\n"+ +" if (!__Atomics_waitAsyncAddresses[0]) setTimeout(__Atomics_pollWaitAsyncAddresses, 10);\n"+ +" __Atomics_waitAsyncAddresses.push([i32a, index, value, maxWaitMilliseconds, promiseResolve]);\n"+ +" return { async: true, value: promise };\n"+ +"};\n"+ +"}", + + // These dependencies are artificial, issued so that we still get the waitAsync polyfill emitted + // if code only calls emscripten_lock/semaphore_async_acquire() + // but not emscripten_atomic_wait_async() directly. + emscripten_lock_async_acquire__deps: ['emscripten_atomic_wait_async'], + emscripten_semaphore_async_acquire__deps: ['emscripten_atomic_wait_async'], + +#endif + + _emscripten_atomic_live_wait_asyncs: '{}', + _emscripten_atomic_live_wait_asyncs_counter: '0', + + emscripten_atomic_wait_async__deps: ['_emscripten_atomic_wait_states', '_emscripten_atomic_live_wait_asyncs', '_emscripten_atomic_live_wait_asyncs_counter'], + emscripten_atomic_wait_async: function(addr, val, asyncWaitFinished, userData, maxWaitMilliseconds) { + var wait = Atomics['waitAsync'](HEAP32, addr >> 2, val, maxWaitMilliseconds); + if (!wait.async) return __emscripten_atomic_wait_states.indexOf(wait.value); + // Increment waitAsync generation counter, account for wraparound in case application does huge amounts of waitAsyncs per second (not sure if possible?) + // Valid counterrange: 0...2^31-1 + var counter = __emscripten_atomic_live_wait_asyncs_counter; + __emscripten_atomic_live_wait_asyncs_counter = Math.max(0, (__emscripten_atomic_live_wait_asyncs_counter+1)|0); + __emscripten_atomic_live_wait_asyncs[counter] = addr >> 2; + wait.value.then((value) => { + if (__emscripten_atomic_live_wait_asyncs[counter]) { + delete __emscripten_atomic_live_wait_asyncs[counter]; + {{{ makeDynCall('viiii', 'asyncWaitFinished') }}}(addr, val, __emscripten_atomic_wait_states.indexOf(value), userData); + } + }); + return -counter; + }, + + emscripten_atomic_cancel_wait_async__deps: ['_emscripten_atomic_live_wait_asyncs'], + emscripten_atomic_cancel_wait_async: function(waitToken) { +#if ASSERTIONS + if (waitToken == 1 /* ATOMICS_WAIT_NOT_EQUAL */) warnOnce('Attempted to call emscripten_atomic_cancel_wait_async() with a value ATOMICS_WAIT_NOT_EQUAL (1) that is not a valid wait token! Check success in return value from call to emscripten_atomic_wait_async()'); + else if (waitToken == 2 /* ATOMICS_WAIT_TIMED_OUT */) warnOnce('Attempted to call emscripten_atomic_cancel_wait_async() with a value ATOMICS_WAIT_TIMED_OUT (2) that is not a valid wait token! Check success in return value from call to emscripten_atomic_wait_async()'); + else if (waitToken > 0) warnOnce('Attempted to call emscripten_atomic_cancel_wait_async() with an invalid wait token value ' + waitToken); +#endif + if (__emscripten_atomic_live_wait_asyncs[waitToken]) { + // Notify the waitAsync waiters on the memory location, so that JavaScript garbage collection can occur + // See https://github.com/WebAssembly/threads/issues/176 + // This has the unfortunate effect of causing spurious wakeup of all other waiters at the address (which + // causes a small performance loss) + Atomics.notify(HEAP32, __emscripten_atomic_live_wait_asyncs[waitToken]); + delete __emscripten_atomic_live_wait_asyncs[waitToken]; + return 0 /* EMSCRIPTEN_RESULT_SUCCESS */; + } + // This waitToken does not exist. + return -5 /* EMSCRIPTEN_RESULT_INVALID_PARAM */; + }, + + emscripten_atomic_cancel_all_wait_asyncs__deps: ['_emscripten_atomic_live_wait_asyncs'], + emscripten_atomic_cancel_all_wait_asyncs: function() { + var waitAsyncs = Object.values(__emscripten_atomic_live_wait_asyncs); + waitAsyncs.forEach((address) => { + Atomics.notify(HEAP32, address); + }); + __emscripten_atomic_live_wait_asyncs = {}; + return waitAsyncs.length; + }, + + emscripten_atomic_cancel_all_wait_asyncs_at_address__deps: ['_emscripten_atomic_live_wait_asyncs'], + emscripten_atomic_cancel_all_wait_asyncs_at_address: function(address) { + address >>= 2; + var numCancelled = 0; + Object.keys(__emscripten_atomic_live_wait_asyncs).forEach((waitToken) => { + if (__emscripten_atomic_live_wait_asyncs[waitToken] == address) { + Atomics.notify(HEAP32, address); + delete __emscripten_atomic_live_wait_asyncs[waitToken]; + ++numCancelled; + } + }); + return numCancelled; + }, + + emscripten_navigator_hardware_concurrency: function() { +#if ENVIRONMENT_MAY_BE_NODE + if (ENVIRONMENT_IS_NODE) return require('os').cpus().length; +#endif + return navigator['hardwareConcurrency']; + }, + + emscripten_atomics_is_lock_free: function(width) { + return Atomics.isLockFree(width); + }, + + emscripten_lock_async_acquire: function(lock, asyncWaitFinished, userData, maxWaitMilliseconds) { + function dispatch(val, ret) { + setTimeout(() => { + {{{ makeDynCall('viiii', 'asyncWaitFinished') }}}(lock, val, /*waitResult=*/ret, userData); + }, 0); + } + function tryAcquireLock() { + do { + var val = Atomics.compareExchange(HEAPU32, lock >> 2, 0/*zero represents lock being free*/, 1/*one represents lock being acquired*/); + if (!val) return dispatch(0, 0/*'ok'*/); + var wait = Atomics['waitAsync'](HEAPU32, lock >> 2, val, maxWaitMilliseconds); + } while(wait.value === 'not-equal'); +#if ASSERTIONS + assert(wait.async || wait.value === 'timed-out'); +#endif + if (wait.async) wait.value.then(tryAcquireLock); + else dispatch(val, 2/*'timed-out'*/); + } + tryAcquireLock(); + }, + + emscripten_semaphore_async_acquire: function(sem, num, asyncWaitFinished, userData, maxWaitMilliseconds) { + function dispatch(idx, ret) { + setTimeout(() => { + {{{ makeDynCall('viiii', 'asyncWaitFinished') }}}(sem, /*val=*/idx, /*waitResult=*/ret, userData); + }, 0); + } + function tryAcquireSemaphore() { + var val = num; + do { + var ret = Atomics.compareExchange(HEAPU32, sem >> 2, + val, /* We expect this many semaphore resoures to be available*/ + val - num /* Acquire 'num' of them */); + if (ret == val) return dispatch(ret/*index of resource acquired*/, 0/*'ok'*/); + val = ret; + var wait = Atomics['waitAsync'](HEAPU32, sem >> 2, ret, maxWaitMilliseconds); + } while(wait.value === 'not-equal'); +#if ASSERTIONS + assert(wait.async || wait.value === 'timed-out'); +#endif + if (wait.async) wait.value.then(tryAcquireSemaphore); + else dispatch(-1/*idx*/, 2/*'timed-out'*/); + } + tryAcquireSemaphore(); + } +}); diff --git a/src/postamble_minimal.js b/src/postamble_minimal.js index f977ccf26e6a..1f2ad681f5bd 100644 --- a/src/postamble_minimal.js +++ b/src/postamble_minimal.js @@ -72,6 +72,10 @@ function initRuntime(asm) { } #endif +#if WASM_WORKERS + if (ENVIRONMENT_IS_WASM_WORKER) return __wasm_worker_initializeRuntime(); +#endif + #if STACK_OVERFLOW_CHECK _emscripten_stack_init(); writeStackCookie(); diff --git a/src/preamble_minimal.js b/src/preamble_minimal.js index 932758d58967..7cdc105b9467 100644 --- a/src/preamble_minimal.js +++ b/src/preamble_minimal.js @@ -54,7 +54,7 @@ var HEAP_DATA_VIEW; #endif function updateGlobalBufferAndViews(b) { -#if ASSERTIONS && USE_PTHREADS +#if ASSERTIONS && SHARED_MEMORY assert(b instanceof SharedArrayBuffer, 'requested a shared WebAssembly.Memory but the returned buffer is not a SharedArrayBuffer, indicating that while the browser has SharedArrayBuffer it does not have WebAssembly threads support - you may need to set a flag'); #endif buffer = b; @@ -80,12 +80,17 @@ if (!ENVIRONMENT_IS_PTHREAD) { #else var wasmMaximumMemory = {{{ INITIAL_MEMORY >>> 16}}}; #endif - wasmMemory = new WebAssembly.Memory({ + + wasmMemory = +#if WASM_WORKERS + Module['mem'] || +#endif + new WebAssembly.Memory({ 'initial': {{{ INITIAL_MEMORY >>> 16 }}} -#if USE_PTHREADS || !ALLOW_MEMORY_GROWTH || MAXIMUM_MEMORY != FOUR_GB +#if SHARED_MEMORY || !ALLOW_MEMORY_GROWTH || MAXIMUM_MEMORY != FOUR_GB , 'maximum': wasmMaximumMemory #endif -#if USE_PTHREADS +#if SHARED_MEMORY , 'shared': true #endif }); diff --git a/src/runtime_strings.js b/src/runtime_strings.js index 3b10d6c3eb3d..0b48fabbd8ea 100644 --- a/src/runtime_strings.js +++ b/src/runtime_strings.js @@ -9,7 +9,7 @@ // Given a pointer 'ptr' to a null-terminated UTF8-encoded string in the given array that contains uint8 values, returns // a copy of that string as a Javascript String object. -#if USE_PTHREADS && TEXTDECODER +#if SHARED_MEMORY && TEXTDECODER /** * UTF8Decoder.decode may not work with a view of a SharedArrayBuffer, see * https://github.com/whatwg/encoding/issues/172 @@ -36,10 +36,10 @@ function TextDecoderWrapper(encoding) { #endif #if TEXTDECODER == 2 -var UTF8Decoder = new TextDecoder{{{ USE_PTHREADS ? 'Wrapper' : ''}}}('utf8'); +var UTF8Decoder = new TextDecoder{{{ SHARED_MEMORY ? 'Wrapper' : ''}}}('utf8'); #else // TEXTDECODER == 2 #if TEXTDECODER -var UTF8Decoder = typeof TextDecoder != 'undefined' ? new TextDecoder{{{ USE_PTHREADS ? 'Wrapper' : ''}}}('utf8') : undefined; +var UTF8Decoder = typeof TextDecoder != 'undefined' ? new TextDecoder{{{ SHARED_MEMORY ? 'Wrapper' : ''}}}('utf8') : undefined; #endif // TEXTDECODER #endif // TEXTDECODER == 2 diff --git a/src/runtime_strings_extra.js b/src/runtime_strings_extra.js index e471aaab37f0..f59e52a6a6be 100644 --- a/src/runtime_strings_extra.js +++ b/src/runtime_strings_extra.js @@ -32,10 +32,10 @@ function stringToAscii(str, outPtr) { // a copy of that string as a Javascript String object. #if TEXTDECODER == 2 -var UTF16Decoder = new TextDecoder{{{ USE_PTHREADS ? 'Wrapper' : ''}}}('utf-16le'); +var UTF16Decoder = new TextDecoder{{{ SHARED_MEMORY ? 'Wrapper' : ''}}}('utf-16le'); #else // TEXTDECODER == 2 #if TEXTDECODER -var UTF16Decoder = typeof TextDecoder != 'undefined' ? new TextDecoder{{{ USE_PTHREADS ? 'Wrapper' : ''}}}('utf-16le') : undefined; +var UTF16Decoder = typeof TextDecoder != 'undefined' ? new TextDecoder{{{ SHARED_MEMORY ? 'Wrapper' : ''}}}('utf-16le') : undefined; #endif // TEXTDECODER #endif // TEXTDECODER == 2 diff --git a/src/settings.js b/src/settings.js index a3d45c7a3f45..92a2b5456075 100644 --- a/src/settings.js +++ b/src/settings.js @@ -1470,6 +1470,12 @@ var IN_TEST_HARNESS = 0; // [compile+link] - affects user code at compile and system libraries at link. var USE_PTHREADS = 0; +// If true, enables support for Wasm Workers. Wasm Workers enable applications +// to create threads using a lightweight web-specific API that builds on top +// of Wasm SharedArrayBuffer + Atomics API. +// [compile+link] - affects user code at compile and system libraries at link. +var WASM_WORKERS = 0; + // In web browsers, Workers cannot be created while the main browser thread // is executing JS/Wasm code, but the main thread must regularly yield back // to the browser event loop for Worker initialization to occur. diff --git a/src/settings_internal.js b/src/settings_internal.js index bd841688a1d0..6515db933ae4 100644 --- a/src/settings_internal.js +++ b/src/settings_internal.js @@ -129,6 +129,12 @@ var WASM_BINARY_FILE = ''; // name of the file containing the pthread *.worker.js, if relevant var PTHREAD_WORKER_FILE = ''; +// name of the file containing the Wasm Worker *.ww.js, if relevant +var WASM_WORKER_FILE = ''; + +// If 1, we are building with SharedArrayBuffer as Wasm Memory. +var SHARED_MEMORY = 0; + // Base URL the source mapfile, if relevant var SOURCE_MAP_BASE = ''; diff --git a/src/shell_minimal.js b/src/shell_minimal.js index d317aa61eb1a..0c957daf6bfc 100644 --- a/src/shell_minimal.js +++ b/src/shell_minimal.js @@ -46,6 +46,10 @@ var ENVIRONMENT_IS_WEB = !ENVIRONMENT_IS_NODE; #endif #endif // ASSERTIONS || USE_PTHREADS +#if WASM_WORKERS +var ENVIRONMENT_IS_WASM_WORKER = Module['$ww']; +#endif + #if ASSERTIONS && ENVIRONMENT_MAY_BE_NODE && ENVIRONMENT_MAY_BE_SHELL if (ENVIRONMENT_IS_NODE && ENVIRONMENT_IS_SHELL) { throw 'unclear environment'; @@ -113,9 +117,12 @@ function ready() { #if INVOKE_RUN && HAS_MAIN #if USE_PTHREADS if (!ENVIRONMENT_IS_PTHREAD) { +#endif +#if WASM_WORKERS + if (!ENVIRONMENT_IS_WASM_WORKER) { #endif run(); -#if USE_PTHREADS +#if USE_PTHREADS || WASM_WORKERS } #endif #else diff --git a/src/wasm2js.js b/src/wasm2js.js index bd3a929e633d..c944e432256d 100644 --- a/src/wasm2js.js +++ b/src/wasm2js.js @@ -19,7 +19,7 @@ WebAssembly = { // not a fully general polyfill. /** @constructor */ Memory: function(opts) { -#if USE_PTHREADS +#if SHARED_MEMORY this.buffer = new SharedArrayBuffer(opts['initial'] * {{{ WASM_PAGE_SIZE }}}); #else this.buffer = new ArrayBuffer(opts['initial'] * {{{ WASM_PAGE_SIZE }}}); @@ -74,7 +74,7 @@ WebAssembly = { then: function(ok) { var module = new WebAssembly.Module(binary); ok({ -#if USE_PTHREADS +#if SHARED_MEMORY 'module': module, #endif 'instance': new WebAssembly.Instance(module) diff --git a/src/wasm_worker.js b/src/wasm_worker.js new file mode 100644 index 000000000000..81e70a771a0f --- /dev/null +++ b/src/wasm_worker.js @@ -0,0 +1,21 @@ +// N.B. The contents of this file are duplicated in src/library_wasm_workers.js +// in variable "_wasmWorkerBlobUrl" (where the contents are pre-minified) If +// doing any changes to this file, be sure to update the contents there too. +onmessage = function(d) { + // The first message sent to the Worker is always the bootstrap message. + // Drop this message listener, it served its purpose of bootstrapping + // the Wasm Module load, and is no longer needed. Let user code register + // any desired message handlers from now on. + onmessage = null; + d = d.data; +#if !MODULARIZE + self.{{{ EXPORT_NAME }}} = d; +#endif + importScripts(d.js); +#if MODULARIZE + {{{ EXPORT_NAME }}}(d); +#endif + // Drop now unneeded references to from the Module object in this Worker, + // these are not needed anymore. + d.wasm = d.mem = d.js = 0; +} diff --git a/system/include/emscripten/wasm_worker.h b/system/include/emscripten/wasm_worker.h new file mode 100644 index 000000000000..72aaa16836ff --- /dev/null +++ b/system/include/emscripten/wasm_worker.h @@ -0,0 +1,299 @@ +#pragma once + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#define emscripten_wasm_worker_t int +#define EMSCRIPTEN_WASM_WORKER_ID_PARENT 0 + +// If not building with Wasm workers enabled (-s WASM_WORKERS=0), returns 0. +emscripten_wasm_worker_t emscripten_create_wasm_worker(void *stackLowestAddress, uint32_t stackSize); + +// Exists, but is a no-op if not building with Wasm Workers enabled (-s WASM_WORKERS=0) +void emscripten_terminate_wasm_worker(emscripten_wasm_worker_t id); + +// Exists, but is a no-op if not building with Wasm Workers enabled (-s WASM_WORKERS=0) +void emscripten_terminate_all_wasm_workers(void); + +// Returns EM_TRUE if the current thread is executing a Wasm Worker, EM_FALSE otherwise. +// Note that calling this function can be relatively slow as it incurs a Wasm->JS transition, +// so avoid calling it in hot paths. +EM_BOOL emscripten_current_thread_is_wasm_worker(void); + +// Returns a unique ID that identifies the calling Wasm Worker. Similar to pthread_self(). +// The main browser thread will return 0 as the ID. First Wasm Worker will return 1, and so on. +uint32_t emscripten_wasm_worker_self_id(void); + +// postMessage()s a function call over to the given Wasm Worker. +// Note that if the Wasm Worker runs in an infinite loop, it will not process the postMessage +// queue to dispatch the function call, until the infinite loop is broken and execution is returned +// back to the Worker event loop. +// Exists, but is a no-op if not building with Wasm Workers enabled (-s WASM_WORKERS=0) +void emscripten_wasm_worker_post_function_v(emscripten_wasm_worker_t id, void (*funcPtr)(void)); +void emscripten_wasm_worker_post_function_vi(emscripten_wasm_worker_t id, void (*funcPtr)(int), int arg0); +void emscripten_wasm_worker_post_function_vii(emscripten_wasm_worker_t id, void (*funcPtr)(int, int), int arg0, int arg1); +void emscripten_wasm_worker_post_function_viii(emscripten_wasm_worker_t id, void (*funcPtr)(int, int, int), int arg0, int arg1, int arg2); +void emscripten_wasm_worker_post_function_vd(emscripten_wasm_worker_t id, void (*funcPtr)(double), double arg0); +void emscripten_wasm_worker_post_function_vdd(emscripten_wasm_worker_t id, void (*funcPtr)(double, double), double arg0, double arg1); +void emscripten_wasm_worker_post_function_vddd(emscripten_wasm_worker_t id, void (*funcPtr)(double, double, double), double arg0, double arg1, double arg2); +void emscripten_wasm_worker_post_function_sig(emscripten_wasm_worker_t id, void *funcPtr, const char *sig, ...); + +#define ATOMICS_WAIT_RESULT_T int + +// Numbering dictated by https://github.com/WebAssembly/threads/blob/master/proposals/threads/Overview.md#wait +#define ATOMICS_WAIT_OK 0 +#define ATOMICS_WAIT_NOT_EQUAL 1 +#define ATOMICS_WAIT_TIMED_OUT 2 + +#define ATOMICS_WAIT_DURATION_INFINITE -1ll + +// Issues the wasm 'memory.atomic.wait32' instruction: +// If the given memory address contains value 'expectedValue', puts the calling thread to sleep to wait for that address to be notified. +// Returns one of the ATOMICS_WAIT_* return codes. +// NOTE: This function takes in the wait value in int64_t nanosecond units. Pass in maxWaitNanoseconds = -1 (or ATOMICS_WAIT_DURATION_INFINITE) to wait infinitely long. +static inline __attribute__((always_inline)) ATOMICS_WAIT_RESULT_T emscripten_wasm_wait_i32(int32_t *address, int expectedValue, int64_t maxWaitNanoseconds) +{ + return __builtin_wasm_memory_atomic_wait32(address, expectedValue, maxWaitNanoseconds); +} + +// Issues the wasm 'memory.atomic.wait64' instruction: +// If the given memory address contains value 'expectedValue', puts the calling thread to sleep to wait for that address to be notified. +// Returns one of the ATOMICS_WAIT_* return codes. +// NOTE: This function takes in the wait value in int64_t nanosecond units. Pass in maxWaitNanoseconds = -1 (or ATOMICS_WAIT_DURATION_INFINITE) to wait infinitely long. +static inline __attribute__((always_inline)) ATOMICS_WAIT_RESULT_T emscripten_wasm_wait_i64(int64_t *address, int64_t expectedValue, int64_t maxWaitNanoseconds) +{ + return __builtin_wasm_memory_atomic_wait64(address, expectedValue, maxWaitNanoseconds); +} + +#define EMSCRIPTEN_NOTIFY_ALL_WAITERS (-1LL) + +// Issues the wasm 'memory.atomic.notify' instruction: +// Notifies the given number of threads waiting on a location. +// Pass count == EMSCRIPTEN_NOTIFY_ALL_WAITERS to notify all waiters on the given location. +// Returns the number of threads that were woken up. +// Note: this function is used to notify both waiters waiting on an i32 and i64 addresses. +static inline __attribute__((always_inline)) int64_t emscripten_wasm_notify(int32_t *address, int64_t count) +{ + return __builtin_wasm_memory_atomic_notify(address, count); +} + +#define EMSCRIPTEN_WAIT_ASYNC_INFINITY __builtin_inf() + +// Represents a pending 'Atomics.waitAsync' wait operation. +#define ATOMICS_WAIT_TOKEN_T int + +#define EMSCRIPTEN_IS_VALID_WAIT_TOKEN(token) ((token) <= 0) + +// Issues the JavaScript 'Atomics.waitAsync' instruction: +// performs an asynchronous wait operation on the main thread. If the given 'address' contains 'value', issues a +// deferred wait that will invoke the specified callback function 'asyncWaitFinished' once that +// address has been notified by another thread. +// NOTE: Unlike functions emscripten_wasm_wait_i32() and emscripten_wasm_wait_i64() which take in the +// wait timeout parameter as int64 nanosecond units, this function takes in the wait timeout parameter +// as double millisecond units. See https://github.com/WebAssembly/threads/issues/175 for more information. +// Pass in maxWaitMilliseconds == EMSCRIPTEN_WAIT_ASYNC_INFINITY (==__builtin_inf()) to wait infinitely long. +// Returns one of: +// - ATOMICS_WAIT_NOT_EQUAL if the waitAsync operation could not be registered since the memory value did not +// contain the value 'value'. +// - ATOMICS_WAIT_TIMED_OUT if the waitAsync operation timeout parameter was <= 0. +// - Any other value: denotes a 'wait token' that can be passed to function emscripten_atomic_cancel_wait_async() +// to unregister an asynchronous wait. You can use the macro EMSCRIPTEN_IS_VALID_WAIT_TOKEN(retval) to check +// if this function returned a valid wait token. +ATOMICS_WAIT_TOKEN_T emscripten_atomic_wait_async(int32_t *address, + uint32_t value, + void (*asyncWaitFinished)(int32_t *address, uint32_t value, ATOMICS_WAIT_RESULT_T waitResult, void *userData), + void *userData, + double maxWaitMilliseconds); + +// Unregisters a pending Atomics.waitAsync operation that was established via a call to emscripten_atomic_wait_async() +// in the calling thread. Pass in the wait token handle that was received as the return value from the wait function. +// Returns EMSCRIPTEN_RESULT_SUCCESS if the cancellation was successful, or EMSCRIPTEN_RESULT_INVALID_PARAM if the +// asynchronous wait has already resolved prior and the callback has already been called. +// NOTE: Because of needing to work around issue https://github.com/WebAssembly/threads/issues/176, calling this +// function has an effect of introducing spurious wakeups to any other threads waiting on the +// same address that the async wait denoted by the token does. This means that in order to safely use this function, +// the mechanisms used in any wait code on that address must be written to be spurious wakeup safe. (this is the +// case for all the synchronization primitives declared in this header, but if you are rolling out your own, you +// need to be aware of this). If https://github.com/tc39/proposal-cancellation/issues/29 is resolved, then the +// spurious wakeups can be avoided. +EMSCRIPTEN_RESULT emscripten_atomic_cancel_wait_async(ATOMICS_WAIT_TOKEN_T waitToken); + +// Cancels all pending async waits in the calling thread. Because of https://github.com/WebAssembly/threads/issues/176, +// if you are using asynchronous waits in your application, and need to be able to let GC reclaim Wasm heap memory +// when deinitializing an application, you *must* call this function to help the GC unpin all necessary memory. +// Otherwise, you can wrap the Wasm content in an iframe and unload the iframe to let GC occur. (navigating away +// from the page or closing that tab will also naturally reclaim the memory) +int emscripten_atomic_cancel_all_wait_asyncs(void); + +// Cancels all pending async waits in the calling thread to the given memory address. +// Returns the number of async waits canceled. +int emscripten_atomic_cancel_all_wait_asyncs_at_address(int32_t *address); + +// Sleeps the calling wasm worker for the given nanoseconds. Calling this function on the main thread +// either results in a TypeError exception (Firefox), or a silent return without waiting (Chrome), +// see https://github.com/WebAssembly/threads/issues/174 +void emscripten_wasm_worker_sleep(int64_t nanoseconds); + +// Returns the value of navigator.hardwareConcurrency, i.e. the number of logical threads available for +// the user agent. NOTE: If the execution environment does not support navigator.hardwareConcurrency, +// this function will return zero to signal no support. (If the value 1 is returned, then it means that +// navigator.hardwareConcurrency is supported, but there is only one logical thread of concurrency available) +int emscripten_navigator_hardware_concurrency(void); + +// Returns the value of the expression "Atomics.isLockFree(byteWidth)": true if the given memory access +// width can be accessed atomically, and false otherwise. Generally will return true +// on 1, 2 and 4 byte accesses. On 8 byte accesses, behavior differs across browsers, see +// - https://bugzilla.mozilla.org/show_bug.cgi?id=1246139 +// - https://bugs.chromium.org/p/chromium/issues/detail?id=1167449 +int emscripten_atomics_is_lock_free(int byteWidth); + +#define emscripten_lock_t volatile uint32_t + +// Use with syntax "emscripten_lock_t l = EMSCRIPTEN_LOCK_T_STATIC_INITIALIZER;" +#define EMSCRIPTEN_LOCK_T_STATIC_INITIALIZER 0 + +void emscripten_lock_init(emscripten_lock_t *lock); + +// Attempts to acquire the specified lock. If the lock is free, then this function acquires +// the lock and immediately returns EM_TRUE. If the lock is not free at the time of the call, +// the calling thread is set to synchronously sleep for at most maxWaitNanoseconds long, until +// another thread releases the lock. If the lock is acquired within that period, the function +// returns EM_TRUE. If the lock is not acquired within the specified period, then the wait +// times out and EM_FALSE is returned. +// NOTE: This function can be only called in a Worker, and not on the main browser thread, +// because the main browser thread cannot synchronously sleep to wait for locks. +EM_BOOL emscripten_lock_wait_acquire(emscripten_lock_t *lock, int64_t maxWaitNanoseconds); + +// Similar to emscripten_lock_wait_acquire(), but instead of waiting for at most a specified +// timeout value, the thread will wait indefinitely long until the lock can be acquired. +// NOTE: The only way to abort this wait is to call emscripten_terminate_wasm_worker() on the +// Worker. +// NOTE: This function can be only called in a Worker, and not on the main browser thread, +// because the main browser thread cannot synchronously sleep to wait for locks. +void emscripten_lock_waitinf_acquire(emscripten_lock_t *lock); + +// Similar to emscripten_lock_wait_acquire(), but instead of placing the calling thread to +// sleep until the lock can be acquired, this function will burn CPU cycles attempting to +// acquire the lock, until the given timeout is met. +// This function can be called in both main thread and in Workers. +// NOTE: The wait period used for this function is specified in milliseconds instead of +// nanoseconds, see https://github.com/WebAssembly/threads/issues/175 for details. +// NOTE: If this function is called on the main thread, be sure to use a reasonable max wait +// value, or otherwise a "slow script dialog" notification can pop up, and can cause the +// browser to stop executing the page. +EM_BOOL emscripten_lock_busyspin_wait_acquire(emscripten_lock_t *lock, double maxWaitMilliseconds); + +// Similar to emscripten_lock_wait_acquire(), but instead of placing the calling thread to +// sleep until the lock can be acquired, this function will burn CPU cycles indefinitely until +// the given lock can be acquired. +// This function can be called in both main thread and in Workers. +// NOTE: The only way to abort this wait is to call emscripten_terminate_wasm_worker() on the +// Worker. If called on the main thread, and the lock cannot be acquired within a reasonable +// time period, this function will *HANG* the browser page content process, and show up +// a "slow script dialog", and/or cause the browser to stop the page. If you call this +// function on the main browser thread, be extra careful to analyze that the given lock will +// be extremely fast to acquire without contention from other threads. +void emscripten_lock_busyspin_waitinf_acquire(emscripten_lock_t *lock); + +// Registers an *asynchronous* lock acquire operation. The calling thread will asynchronously +// try to obtain the given lock after the calling thread yields back to the event loop. If the +// attempt is successful within maxWaitMilliseconds period, then the given callback asyncWaitFinished +// is called with waitResult == ATOMICS_WAIT_OK. If the lock is not acquired within the timeout +// period, then the callback asyncWaitFinished is called with waitResult == ATOMICS_WAIT_TIMED_OUT. +// NOTE: Unlike function emscripten_lock_wait_acquire() which takes in the +// wait timeout parameter as int64 nanosecond units, this function takes in the wait timeout parameter +// as double millisecond units. See https://github.com/WebAssembly/threads/issues/175 for more information. +// NOTE: This function can be called in both main thread and in Workers. If you use this API in Worker, +// you cannot utilise an infinite loop programming model. +void emscripten_lock_async_acquire(emscripten_lock_t *lock, + void (*asyncWaitFinished)(volatile void *address, uint32_t value, ATOMICS_WAIT_RESULT_T waitResult, void *userData), + void *userData, + double maxWaitMilliseconds); + +// Attempts to acquire a lock, returning EM_TRUE if successful. If the lock is already held, +// this function will not sleep to wait until the lock is released, but immediately returns +// EM_FALSE. +// This function can be called on both main thread and in Workers. +EM_BOOL emscripten_lock_try_acquire(emscripten_lock_t *lock); + +// Unlocks the specified lock for another thread to access. Note that locks are extermely +// lightweight, there is no "lock owner" tracking: this function does not actually check +// whether the calling thread owns the specified lock, but any thread can call this function +// to release a lock on behalf of whichever thread owns it. +// This function can be called on both main thread and in Workers. +void emscripten_lock_release(emscripten_lock_t *lock); + +#define emscripten_semaphore_t volatile uint32_t + +// Use with syntax emscripten_semaphore_t s = EMSCRIPTEN_SEMAPHORE_T_STATIC_INITIALIZER(num); +#define EMSCRIPTEN_SEMAPHORE_T_STATIC_INITIALIZER(num) ((int)(num)) + +void emscripten_semaphore_init(emscripten_semaphore_t *sem, int num); + +// main thread, try acquire num instances, but do not sleep to wait if not available. +// Returns idx that was acquired or -1 if acquire failed. +int emscripten_semaphore_try_acquire(emscripten_semaphore_t *sem, int num); + +// main thread, poll to try acquire num instances. Returns idx that was acquired. If you use this API in Worker, you cannot run an infinite loop. +void emscripten_semaphore_async_acquire(emscripten_semaphore_t *sem, + int num, + void (*asyncWaitFinished)(volatile void *address, uint32_t idx, ATOMICS_WAIT_RESULT_T result, void *userData), + void *userData, + double maxWaitMilliseconds); + +// worker, sleep to acquire num instances. Returns idx that was acquired, or -1 if timed out unable to acquire. +int emscripten_semaphore_wait_acquire(emscripten_semaphore_t *sem, int num, int64_t maxWaitNanoseconds); + +// worker, sleep infinitely long to acquire num instances. Returns idx that was acquired. +int emscripten_semaphore_waitinf_acquire(emscripten_semaphore_t *sem, int num); + +// Releases the given number of resources back to the semaphore. Note +// that the ownership of resources is completely conceptual - there is +// no actual checking that the calling thread had previously acquired +// that many resouces, so programs need to keep check of their +// semaphore usage consistency themselves. +// Returns how many resources were available in the semaphore before the +// new resources were released back to the semaphore. (i.e. the index where +// the resource was put back to) +// [main thread or worker] +uint32_t emscripten_semaphore_release(emscripten_semaphore_t *sem, int num); + +// Condition variable is an object that can be waited on, and another thread can signal, while coordinating an access to a related mutex. +#define emscripten_condvar_t volatile uint32_t + +// Use with syntax emscripten_condvar_t cv = EMSCRIPTEN_CONDVAR_T_STATIC_INITIALIZER; +#define EMSCRIPTEN_CONDVAR_T_STATIC_INITIALIZER ((int)(0)) + +// Creates a new condition variable to the given memory location. +void emscripten_condvar_init(emscripten_condvar_t *condvar); + +// Atomically performs the following: +// 1. releases the given lock. The lock should (but does not strictly need to) be held by the calling thread prior to this call. +// 2. sleep the calling thread to wait for the specified condition variable to be signaled. +// 3. once the sleep has finished (another thread has signaled the condition variable), the calling thread wakes up +// and reacquires the lock prior to returning from this function. +void emscripten_condvar_waitinf(emscripten_condvar_t *condvar, emscripten_lock_t *lock); + +// Same as the above, except that an attempt to wait for the condition variable to become true is only performed for a maximum duration. +// On success (no timeout), this function will return EM_TRUE. If the wait times out, this function will return EM_FALSE. In this case, +// the calling thread will not try to reacquire the lock. +EM_BOOL emscripten_condvar_wait(emscripten_condvar_t *condvar, emscripten_lock_t *lock, int64_t maxWaitNanoseconds); + +// Asynchronously wait for the given condition variable to signal. +ATOMICS_WAIT_TOKEN_T emscripten_condvar_wait_async(emscripten_condvar_t *condvar, + emscripten_lock_t *lock, + void (*asyncWaitFinished)(int32_t *address, uint32_t value, ATOMICS_WAIT_RESULT_T waitResult, void *userData), + void *userData, + double maxWaitMilliseconds); + +// Signals the given number of waiters on the specified condition variable. +// Pass numWaitersToSignal == EMSCRIPTEN_NOTIFY_ALL_WAITERS to wake all waiters ("broadcast" operation). +void emscripten_condvar_signal(emscripten_condvar_t *condvar, int64_t numWaitersToSignal); + +#ifdef __cplusplus +} +#endif diff --git a/system/lib/wasm_worker/library_wasm_worker.c b/system/lib/wasm_worker/library_wasm_worker.c new file mode 100644 index 000000000000..5b6ddbbbfc18 --- /dev/null +++ b/system/lib/wasm_worker/library_wasm_worker.c @@ -0,0 +1,186 @@ +#include +#include + +// Internal implementation function in JavaScript side that emscripten_create_wasm_worker() calls to +// to perform the wasm worker creation. +emscripten_wasm_worker_t _emscripten_create_wasm_worker(void *stackLowestAddress, uint32_t stackSize); + +emscripten_wasm_worker_t emscripten_create_wasm_worker(void *stackLowestAddress, uint32_t stackSize) +{ + uintptr_t stackBase = ((uintptr_t)stackLowestAddress + 15) & -16; + stackSize = ((uintptr_t)stackLowestAddress + stackSize - stackBase) & -16; + return _emscripten_create_wasm_worker((void*)stackBase, stackSize); +} + +void emscripten_wasm_worker_sleep(int64_t nsecs) +{ + int32_t addr = 0; + emscripten_wasm_wait_i32(&addr, 0, nsecs); +} + +void emscripten_lock_init(emscripten_lock_t *lock) +{ + emscripten_atomic_store_u32((void*)lock, EMSCRIPTEN_LOCK_T_STATIC_INITIALIZER); +} + +EM_BOOL emscripten_lock_wait_acquire(emscripten_lock_t *lock, int64_t maxWaitNanoseconds) +{ + emscripten_lock_t val = emscripten_atomic_cas_u32((void*)lock, 0, 1); + if (!val) return EM_TRUE; + int64_t waitEnd = (int64_t)(emscripten_performance_now() * 1e6) + maxWaitNanoseconds; + while(maxWaitNanoseconds > 0) + { + emscripten_wasm_wait_i32((int32_t*)lock, val, maxWaitNanoseconds); + val = emscripten_atomic_cas_u32((void*)lock, 0, 1); + if (!val) return EM_TRUE; + maxWaitNanoseconds = waitEnd - (int64_t)(emscripten_performance_now() * 1e6); + } + return EM_FALSE; +} + +void emscripten_lock_waitinf_acquire(emscripten_lock_t *lock) +{ + emscripten_lock_t val; + do + { + val = emscripten_atomic_cas_u32((void*)lock, 0, 1); + if (val) + emscripten_wasm_wait_i32((int32_t*)lock, val, ATOMICS_WAIT_DURATION_INFINITE); + } while(val); +} + +EM_BOOL emscripten_lock_busyspin_wait_acquire(emscripten_lock_t *lock, double maxWaitMilliseconds) +{ + emscripten_lock_t val = emscripten_atomic_cas_u32((void*)lock, 0, 1); + if (!val) return EM_TRUE; + + double t = emscripten_performance_now(); + double waitEnd = t + maxWaitMilliseconds; + while(t < waitEnd) + { + val = emscripten_atomic_cas_u32((void*)lock, 0, 1); + if (!val) return EM_TRUE; + t = emscripten_performance_now(); + } + return EM_FALSE; +} + +void emscripten_lock_busyspin_waitinf_acquire(emscripten_lock_t *lock) +{ + emscripten_lock_t val; + do + { + val = emscripten_atomic_cas_u32((void*)lock, 0, 1); + } while(val); +} + +EM_BOOL emscripten_lock_try_acquire(emscripten_lock_t *lock) +{ + emscripten_lock_t val = emscripten_atomic_cas_u32((void*)lock, 0, 1); + return !val; +} + +void emscripten_lock_release(emscripten_lock_t *lock) +{ + emscripten_atomic_store_u32((void*)lock, 0); + emscripten_wasm_notify((int32_t*)lock, 1); +} + +void emscripten_semaphore_init(emscripten_semaphore_t *sem, int num) +{ + emscripten_atomic_store_u32((void*)sem, num); +} + +int emscripten_semaphore_try_acquire(emscripten_semaphore_t *sem, int num) +{ + uint32_t val = num; + for(;;) + { + uint32_t ret = emscripten_atomic_cas_u32((void*)sem, val, val - num); + if (ret == val) return val - num; + if (ret < num) return -1; + val = ret; + } +} + +int emscripten_semaphore_wait_acquire(emscripten_semaphore_t *sem, int num, int64_t maxWaitNanoseconds) +{ + int val = emscripten_atomic_load_u32((void*)sem); + for(;;) + { + while(val < num) + { + // TODO: Shave off maxWaitNanoseconds + ATOMICS_WAIT_RESULT_T waitResult = emscripten_wasm_wait_i32((int32_t*)sem, val, maxWaitNanoseconds); + if (waitResult == ATOMICS_WAIT_TIMED_OUT) return -1; + val = emscripten_atomic_load_u32((void*)sem); + } + int ret = (int)emscripten_atomic_cas_u32((void*)sem, val, val - num); + if (ret == val) return val - num; + val = ret; + } +} + +int emscripten_semaphore_waitinf_acquire(emscripten_semaphore_t *sem, int num) +{ + int val = emscripten_atomic_load_u32((void*)sem); + for(;;) + { + while(val < num) + { + emscripten_wasm_wait_i32((int32_t*)sem, val, ATOMICS_WAIT_DURATION_INFINITE); + val = emscripten_atomic_load_u32((void*)sem); + } + int ret = (int)emscripten_atomic_cas_u32((void*)sem, val, val - num); + if (ret == val) return val - num; + val = ret; + } +} + +uint32_t emscripten_semaphore_release(emscripten_semaphore_t *sem, int num) +{ + uint32_t ret = emscripten_atomic_add_u32((void*)sem, num); + emscripten_wasm_notify((int*)sem, num); + return ret; +} + +void emscripten_condvar_init(emscripten_condvar_t *condvar) +{ + *condvar = EMSCRIPTEN_CONDVAR_T_STATIC_INITIALIZER; +} + +void emscripten_condvar_waitinf(emscripten_condvar_t *condvar, emscripten_lock_t *lock) +{ + int val = emscripten_atomic_load_u32((void*)condvar); + emscripten_lock_release(lock); + emscripten_wasm_wait_i32((int32_t*)condvar, val, ATOMICS_WAIT_DURATION_INFINITE); + emscripten_lock_waitinf_acquire(lock); +} + +int emscripten_condvar_wait(emscripten_condvar_t *condvar, emscripten_lock_t *lock, int64_t maxWaitNanoseconds) +{ + int val = emscripten_atomic_load_u32((void*)condvar); + emscripten_lock_release(lock); + int waitValue = emscripten_wasm_wait_i32((int32_t*)condvar, val, maxWaitNanoseconds); + if (waitValue == ATOMICS_WAIT_TIMED_OUT) + return EM_FALSE; + + return emscripten_lock_wait_acquire(lock, maxWaitNanoseconds); +} + +ATOMICS_WAIT_TOKEN_T emscripten_condvar_wait_async(emscripten_condvar_t *condvar, + emscripten_lock_t *lock, + void (*asyncWaitFinished)(int32_t *address, uint32_t value, ATOMICS_WAIT_RESULT_T waitResult, void *userData), + void *userData, + double maxWaitMilliseconds) +{ + int val = emscripten_atomic_load_u32((void*)condvar); + emscripten_lock_release(lock); + return emscripten_atomic_wait_async((int32_t *)condvar, val, asyncWaitFinished, userData, maxWaitMilliseconds); +} + +void emscripten_condvar_signal(emscripten_condvar_t *condvar, int64_t numWaitersToSignal) +{ + emscripten_atomic_add_u32((void*)condvar, 1); + emscripten_wasm_notify((int*)condvar, numWaitersToSignal); +} diff --git a/tests/report_result.h b/tests/report_result.h index cd0ceca233dc..39f2e5c83007 100644 --- a/tests/report_result.h +++ b/tests/report_result.h @@ -21,7 +21,7 @@ void _MaybeReportResult(int result, int sync); } #endif -#if defined __EMSCRIPTEN__ && defined __EMSCRIPTEN_PTHREADS__ +#if defined __EMSCRIPTEN__ && defined __EMSCRIPTEN_PTHREADS__ && !defined(__EMSCRIPTEN_WASM_WORKERS__) #include #include #define REPORT_RESULT(result) emscripten_async_run_in_main_runtime_thread(EM_FUNC_SIG_VII, _ReportResult, (result), 0) diff --git a/tests/test_browser.py b/tests/test_browser.py index ca4012b7aec2..d3b78b72a1fe 100644 --- a/tests/test_browser.py +++ b/tests/test_browser.py @@ -5154,6 +5154,146 @@ def test_wasm2js_fallback_on_wasm_compilation_failure(self): def test_system(self): self.btest_exit(test_file('system.c')) + # Tests emscripten_create_wasm_worker() and emscripten_current_thread_is_wasm_worker() functions + def test_wasm_worker_hello(self): + self.btest(path_from_root('tests', 'wasm_worker', 'hello_wasm_worker.c'), + expected='0', + args=['-s', 'WASM_WORKERS=1', '-s', 'MINIMAL_RUNTIME=1']) + + # Tests emscripten_wasm_worker_self_id() function + def test_wasm_worker_self_id(self): + self.btest(path_from_root('tests', 'wasm_worker', 'wasm_worker_self_id.c'), + expected='0', + args=['-s', 'WASM_WORKERS=1', '-s', 'MINIMAL_RUNTIME=1']) + + # Tests emscripten_wasm_worker_sleep() + def test_wasm_worker_sleep(self): + self.btest(path_from_root('tests', 'wasm_worker', 'wasm_worker_sleep.c'), + expected='1', + args=['-s', 'WASM_WORKERS=1', '-s', 'MINIMAL_RUNTIME=1']) + + # Tests emscripten_terminate_wasm_worker() + def test_wasm_worker_terminate(self): + self.btest(path_from_root('tests', 'wasm_worker', 'terminate_wasm_worker.c'), + expected='0', + args=['-s', 'WASM_WORKERS=1', '-s', 'MINIMAL_RUNTIME=1']) + + # Tests emscripten_terminate_all_wasm_workers() + def test_wasm_worker_terminate_all(self): + self.btest(path_from_root('tests', 'wasm_worker', 'terminate_all_wasm_workers.c'), + expected='0', + args=['-s', 'WASM_WORKERS=1', '-s', 'MINIMAL_RUNTIME=1']) + + # Tests emscripten_wasm_worker_post_function_*() API + def test_wasm_worker_post_function(self): + self.btest(path_from_root('tests', 'wasm_worker', 'post_function.c'), + expected='8', + args=['-s', 'WASM_WORKERS=1', '-s', 'MINIMAL_RUNTIME=1']) + + # Tests emscripten_wasm_worker_post_function_*() API and EMSCRIPTEN_WASM_WORKER_ID_PARENT + # to send a message back from Worker to its parent thread. + def test_wasm_worker_post_function_to_main_thread(self): + self.btest(path_from_root('tests', 'wasm_worker', 'post_function_to_main_thread.c'), + expected='10', + args=['-s', 'WASM_WORKERS=1', '-s', 'MINIMAL_RUNTIME=1']) + + # Tests emscripten_navigator_hardware_concurrency() and emscripten_atomics_is_lock_free() + def test_wasm_worker_hardware_concurrency_is_lock_free(self): + self.btest(path_from_root('tests', 'wasm_worker', 'hardware_concurrency_is_lock_free.c'), + expected='0', + args=['-s', 'WASM_WORKERS=1', '-s', 'MINIMAL_RUNTIME=1']) + + # Tests emscripten_wasm_wait_i32() and emscripten_wasm_notify() functions. + def test_wasm_worker_wait32_notify(self): + self.btest(path_from_root('tests', 'wasm_worker', 'wait32_notify.c'), + expected='2', + args=['-s', 'WASM_WORKERS=1', '-s', 'MINIMAL_RUNTIME=1']) + + # Tests emscripten_wasm_wait_i64() and emscripten_wasm_notify() functions. + def test_wasm_worker_wait64_notify(self): + self.btest(path_from_root('tests', 'wasm_worker', 'wait64_notify.c'), + expected='2', + args=['-s', 'WASM_WORKERS=1', '-s', 'MINIMAL_RUNTIME=1']) + + # Tests emscripten_atomic_wait_async() function. + def test_wasm_worker_wait_async(self): + self.btest(path_from_root('tests', 'wasm_worker', 'wait_async.c'), + expected='0', + args=['-s', 'WASM_WORKERS=1', '-s', 'MINIMAL_RUNTIME=1']) + + # Tests emscripten_atomic_cancel_wait_async() function. + def test_wasm_worker_cancel_wait_async(self): + self.btest(path_from_root('tests', 'wasm_worker', 'cancel_wait_async.c'), + expected='1', + args=['-s', 'WASM_WORKERS=1', '-s', 'MINIMAL_RUNTIME=1']) + + # Tests emscripten_atomic_cancel_all_wait_asyncs() function. + def test_wasm_worker_cancel_all_wait_asyncs(self): + self.btest(path_from_root('tests', 'wasm_worker', 'cancel_all_wait_asyncs.c'), + expected='1', + args=['-s', 'WASM_WORKERS=1', '-s', 'MINIMAL_RUNTIME=1']) + + # Tests emscripten_atomic_cancel_all_wait_asyncs_at_address() function. + def test_wasm_worker_cancel_all_wait_asyncs_at_address(self): + self.btest(path_from_root('tests', 'wasm_worker', 'cancel_all_wait_asyncs_at_address.c'), + expected='1', + args=['-s', 'WASM_WORKERS=1', '-s', 'MINIMAL_RUNTIME=1']) + + # Tests emscripten_lock_init(), emscripten_lock_waitinf_acquire() and emscripten_lock_release() + def test_wasm_worker_lock_waitinf(self): + self.btest(path_from_root('tests', 'wasm_worker', 'lock_waitinf_acquire.c'), + expected='4000', + args=['-s', 'WASM_WORKERS=1', '-s', 'MINIMAL_RUNTIME=1']) + + # Tests emscripten_lock_wait_acquire() and emscripten_lock_try_acquire() in Worker. + def test_wasm_worker_lock_wait(self): + self.btest(path_from_root('tests', 'wasm_worker', 'lock_wait_acquire.c'), + expected='0', + args=['-s', 'WASM_WORKERS=1', '-s', 'MINIMAL_RUNTIME=1']) + + # Tests emscripten_lock_wait_acquire() between two Wasm Workers. + def test_wasm_worker_lock_wait2(self): + self.btest(path_from_root('tests', 'wasm_worker', 'lock_wait_acquire2.c'), + expected='0', + args=['-s', 'WASM_WORKERS=1', '-s', 'MINIMAL_RUNTIME=1']) + + # Tests emscripten_lock_async_acquire() function. + def test_wasm_worker_lock_async_acquire(self): + self.btest(path_from_root('tests', 'wasm_worker', 'lock_async_acquire.c'), + expected='0', + args=['-s', 'WASM_WORKERS=1', '-s', 'MINIMAL_RUNTIME=1']) + + # Tests emscripten_lock_busyspin_wait_acquire() in Worker and main thread. + def test_wasm_worker_lock_busyspin_wait(self): + self.btest(path_from_root('tests', 'wasm_worker', 'lock_busyspin_wait_acquire.c'), + expected='0', + args=['-s', 'WASM_WORKERS=1', '-s', 'MINIMAL_RUNTIME=1']) + + # Tests emscripten_lock_busyspin_waitinf_acquire() in Worker and main thread. + def test_wasm_worker_lock_busyspin_waitinf(self): + self.btest(path_from_root('tests', 'wasm_worker', 'lock_busyspin_waitinf_acquire.c'), + expected='1', + args=['-s', 'WASM_WORKERS=1', '-s', 'MINIMAL_RUNTIME=1']) + + # Tests that proxied JS functions cannot be called from Wasm Workers + def test_wasm_worker_no_proxied_js_functions(self): + self.btest(path_from_root('tests', 'wasm_worker', 'no_proxied_js_functions.c'), + expected='0', + args=['--js-library', path_from_root('tests', 'wasm_worker', 'no_proxied_js_functions.js'), + '-s', 'WASM_WORKERS=1', '-s', 'MINIMAL_RUNTIME=1', '-s', 'ASSERTIONS=1']) + + # Tests emscripten_semaphore_init(), emscripten_semaphore_waitinf_acquire() and emscripten_semaphore_release() + def test_wasm_worker_semaphore_waitinf_acquire(self): + self.btest(path_from_root('tests', 'wasm_worker', 'semaphore_waitinf_acquire.c'), + expected='0', + args=['-s', 'WASM_WORKERS=1', '-s', 'MINIMAL_RUNTIME=1']) + + # Tests emscripten_semaphore_try_acquire() on the main thread + def test_wasm_worker_semaphore_try_acquire(self): + self.btest(path_from_root('tests', 'wasm_worker', 'semaphore_try_acquire.c'), + expected='0', + args=['-s', 'WASM_WORKERS=1', '-s', 'MINIMAL_RUNTIME=1']) + @no_firefox('no 4GB support yet') @require_v8 def test_zzz_zzz_4gb(self): diff --git a/tests/wasm_worker/cancel_all_wait_asyncs.c b/tests/wasm_worker/cancel_all_wait_asyncs.c new file mode 100644 index 000000000000..4823cc165126 --- /dev/null +++ b/tests/wasm_worker/cancel_all_wait_asyncs.c @@ -0,0 +1,80 @@ +#include +#include +#include +#include + +// Test emscripten_atomic_cancel_all_wait_asyncs() function. + +EM_JS(void, console_log, (char* str), { + console.log(UTF8ToString(str)); +}); + +volatile int32_t addr = 1; + +EM_BOOL testSucceeded = 1; + +void asyncWaitFinishedShouldNotBeCalled(int32_t *ptr, uint32_t val, ATOMICS_WAIT_RESULT_T waitResult, void *userData) +{ + console_log("asyncWaitFinishedShouldNotBeCalled"); + testSucceeded = 0; + assert(0); // We should not reach here +} + +void asyncWaitFinishedShouldBeCalled(int32_t *ptr, uint32_t val, ATOMICS_WAIT_RESULT_T waitResult, void *userData) +{ + console_log("asyncWaitFinishedShouldBeCalled"); +#ifdef REPORT_RESULT + REPORT_RESULT(testSucceeded); +#endif +} + +int main() +{ + console_log("Async waiting on address should give a wait token"); + ATOMICS_WAIT_TOKEN_T ret = emscripten_atomic_wait_async((int32_t*)&addr, 1, asyncWaitFinishedShouldNotBeCalled, (void*)42, EMSCRIPTEN_WAIT_ASYNC_INFINITY); + assert(EMSCRIPTEN_IS_VALID_WAIT_TOKEN(ret)); + + console_log("Async waiting on address should give a wait token"); + ATOMICS_WAIT_TOKEN_T ret2 = emscripten_atomic_wait_async((int32_t*)&addr, 1, asyncWaitFinishedShouldNotBeCalled, (void*)42, EMSCRIPTEN_WAIT_ASYNC_INFINITY); + assert(EMSCRIPTEN_IS_VALID_WAIT_TOKEN(ret2)); + + console_log("Canceling all async waits should return the number of waits cancelled"); + int numCancelled = emscripten_atomic_cancel_all_wait_asyncs(); + assert(numCancelled == 2); + + console_log("Canceling an async wait that has already been cancelled should give an invalid param"); + EMSCRIPTEN_RESULT r = emscripten_atomic_cancel_wait_async(ret); + assert(r == EMSCRIPTEN_RESULT_INVALID_PARAM); + + console_log("Canceling an async wait that has already been cancelled should give an invalid param"); + r = emscripten_atomic_cancel_wait_async(ret2); + assert(r == EMSCRIPTEN_RESULT_INVALID_PARAM); + + console_log("Notifying an async wait should not trigger the callback function"); + int64_t numWoken = emscripten_wasm_notify((int32_t*)&addr, EMSCRIPTEN_NOTIFY_ALL_WAITERS); + + console_log("Notifying an async wait should return 0 threads woken"); + assert(numWoken == 0); + + addr = 2; + console_log("Notifying an async wait even after changed value should not trigger the callback function"); + numWoken = emscripten_wasm_notify((int32_t*)&addr, EMSCRIPTEN_NOTIFY_ALL_WAITERS); + + console_log("Notifying an async wait should return 0 threads woken"); + assert(numWoken == 0); + + console_log("Async waiting on address should give a wait token"); + ret = emscripten_atomic_wait_async((int32_t*)&addr, 2, asyncWaitFinishedShouldBeCalled, (void*)42, EMSCRIPTEN_WAIT_ASYNC_INFINITY); + assert(EMSCRIPTEN_IS_VALID_WAIT_TOKEN(ret)); + +#if 0 + console_log("Notifying an async wait without value changing should still trigger the callback"); + numWoken = emscripten_wasm_notify((int32_t*)&addr, EMSCRIPTEN_NOTIFY_ALL_WAITERS); + assert(numWoken == 1); +#else + // TODO: Switch to the above test instead after the Atomics.waitAsync() polyfill is dropped. + addr = 3; + console_log("Notifying an async wait after value changing should trigger the callback"); + numWoken = emscripten_wasm_notify((int32_t*)&addr, EMSCRIPTEN_NOTIFY_ALL_WAITERS); +#endif +} diff --git a/tests/wasm_worker/cancel_all_wait_asyncs_at_address.c b/tests/wasm_worker/cancel_all_wait_asyncs_at_address.c new file mode 100644 index 000000000000..c9192e796a34 --- /dev/null +++ b/tests/wasm_worker/cancel_all_wait_asyncs_at_address.c @@ -0,0 +1,84 @@ +#include +#include +#include +#include + +// Test emscripten_atomic_cancel_all_wait_asyncs() function. + +EM_JS(void, console_log, (char* str), { + console.log(UTF8ToString(str)); +}); + +volatile int32_t addr = 1; + +EM_BOOL testSucceeded = 1; + +void asyncWaitFinishedShouldNotBeCalled(int32_t *ptr, uint32_t val, ATOMICS_WAIT_RESULT_T waitResult, void *userData) +{ + console_log("asyncWaitFinishedShouldNotBeCalled"); + testSucceeded = 0; + assert(0); // We should not reach here +} + +void asyncWaitFinishedShouldBeCalled(int32_t *ptr, uint32_t val, ATOMICS_WAIT_RESULT_T waitResult, void *userData) +{ + console_log("asyncWaitFinishedShouldBeCalled"); +#ifdef REPORT_RESULT + REPORT_RESULT(testSucceeded); +#endif +} + +int main() +{ + console_log("Async waiting on address should give a wait token"); + ATOMICS_WAIT_TOKEN_T ret = emscripten_atomic_wait_async((int32_t*)&addr, 1, asyncWaitFinishedShouldNotBeCalled, (void*)42, EMSCRIPTEN_WAIT_ASYNC_INFINITY); + assert(EMSCRIPTEN_IS_VALID_WAIT_TOKEN(ret)); + + console_log("Async waiting on address should give a wait token"); + ATOMICS_WAIT_TOKEN_T ret2 = emscripten_atomic_wait_async((int32_t*)&addr, 1, asyncWaitFinishedShouldNotBeCalled, (void*)42, EMSCRIPTEN_WAIT_ASYNC_INFINITY); + assert(EMSCRIPTEN_IS_VALID_WAIT_TOKEN(ret2)); + + console_log("Canceling all async waits at wait address should return the number of waits cancelled"); + int numCancelled = emscripten_atomic_cancel_all_wait_asyncs_at_address((int32_t*)&addr); + assert(numCancelled == 2); + + console_log("Canceling all async waits at some other address should return 0"); + numCancelled = emscripten_atomic_cancel_all_wait_asyncs_at_address((int32_t*)0xbad); + assert(numCancelled == 0); + + console_log("Canceling an async wait that has already been cancelled should give an invalid param"); + EMSCRIPTEN_RESULT r = emscripten_atomic_cancel_wait_async(ret); + assert(r == EMSCRIPTEN_RESULT_INVALID_PARAM); + + console_log("Canceling an async wait that has already been cancelled should give an invalid param"); + r = emscripten_atomic_cancel_wait_async(ret2); + assert(r == EMSCRIPTEN_RESULT_INVALID_PARAM); + + console_log("Notifying an async wait should not trigger the callback function"); + int64_t numWoken = emscripten_wasm_notify((int32_t*)&addr, EMSCRIPTEN_NOTIFY_ALL_WAITERS); + + console_log("Notifying an async wait should return 0 threads woken"); + assert(numWoken == 0); + + addr = 2; + console_log("Notifying an async wait even after changed value should not trigger the callback function"); + numWoken = emscripten_wasm_notify((int32_t*)&addr, EMSCRIPTEN_NOTIFY_ALL_WAITERS); + + console_log("Notifying an async wait should return 0 threads woken"); + assert(numWoken == 0); + + console_log("Async waiting on address should give a wait token"); + ret = emscripten_atomic_wait_async((int32_t*)&addr, 2, asyncWaitFinishedShouldBeCalled, (void*)42, EMSCRIPTEN_WAIT_ASYNC_INFINITY); + assert(EMSCRIPTEN_IS_VALID_WAIT_TOKEN(ret)); + +#if 0 + console_log("Notifying an async wait without value changing should still trigger the callback"); + numWoken = emscripten_wasm_notify((int32_t*)&addr, EMSCRIPTEN_NOTIFY_ALL_WAITERS); + assert(numWoken == 1); +#else + // TODO: Switch to the above test instead after the Atomics.waitAsync() polyfill is dropped. + addr = 3; + console_log("Notifying an async wait after value changing should trigger the callback"); + numWoken = emscripten_wasm_notify((int32_t*)&addr, EMSCRIPTEN_NOTIFY_ALL_WAITERS); +#endif +} diff --git a/tests/wasm_worker/cancel_wait_async.c b/tests/wasm_worker/cancel_wait_async.c new file mode 100644 index 000000000000..c2822630791b --- /dev/null +++ b/tests/wasm_worker/cancel_wait_async.c @@ -0,0 +1,72 @@ +#include +#include +#include +#include + +// Test emscripten_cancel_wait_async() function. + +EM_JS(void, console_log, (char* str), { + console.log(UTF8ToString(str)); +}); + +volatile int32_t addr = 1; + +EM_BOOL testSucceeded = 1; + +void asyncWaitFinishedShouldNotBeCalled(int32_t *ptr, uint32_t val, ATOMICS_WAIT_RESULT_T waitResult, void *userData) +{ + console_log("asyncWaitFinishedShouldNotBeCalled"); + testSucceeded = 0; + assert(0); // We should not reach here +} + +void asyncWaitFinishedShouldBeCalled(int32_t *ptr, uint32_t val, ATOMICS_WAIT_RESULT_T waitResult, void *userData) +{ + console_log("asyncWaitFinishedShouldBeCalled"); +#ifdef REPORT_RESULT + REPORT_RESULT(testSucceeded); +#endif +} + +int main() +{ + console_log("Async waiting on address should give a wait token"); + ATOMICS_WAIT_TOKEN_T ret = emscripten_atomic_wait_async((int32_t*)&addr, 1, asyncWaitFinishedShouldNotBeCalled, (void*)42, EMSCRIPTEN_WAIT_ASYNC_INFINITY); + assert(EMSCRIPTEN_IS_VALID_WAIT_TOKEN(ret)); + + console_log("Canceling an async wait should succeed"); + EMSCRIPTEN_RESULT r = emscripten_atomic_cancel_wait_async(ret); + assert(r == EMSCRIPTEN_RESULT_SUCCESS); + + console_log("Canceling an async wait a second time should give invalid param"); + r = emscripten_atomic_cancel_wait_async(ret); + assert(r == EMSCRIPTEN_RESULT_INVALID_PARAM); + + console_log("Notifying an async wait should not trigger the callback function"); + int64_t numWoken = emscripten_wasm_notify((int32_t*)&addr, EMSCRIPTEN_NOTIFY_ALL_WAITERS); + + console_log("Notifying an async wait should return 0 threads woken"); + assert(numWoken == 0); + + addr = 2; + console_log("Notifying an async wait even after changed value should not trigger the callback function"); + numWoken = emscripten_wasm_notify((int32_t*)&addr, EMSCRIPTEN_NOTIFY_ALL_WAITERS); + + console_log("Notifying an async wait should return 0 threads woken"); + assert(numWoken == 0); + + console_log("Async waiting on address should give a wait token"); + ret = emscripten_atomic_wait_async((int32_t*)&addr, 2, asyncWaitFinishedShouldBeCalled, (void*)42, EMSCRIPTEN_WAIT_ASYNC_INFINITY); + assert(EMSCRIPTEN_IS_VALID_WAIT_TOKEN(ret)); + +#if 0 + console_log("Notifying an async wait without value changing should still trigger the callback"); + numWoken = emscripten_wasm_notify((int32_t*)&addr, EMSCRIPTEN_NOTIFY_ALL_WAITERS); + assert(numWoken == 1); +#else + // TODO: Switch to the above test instead after the Atomics.waitAsync() polyfill is dropped. + addr = 3; + console_log("Notifying an async wait after value changing should trigger the callback"); + numWoken = emscripten_wasm_notify((int32_t*)&addr, EMSCRIPTEN_NOTIFY_ALL_WAITERS); +#endif +} diff --git a/tests/wasm_worker/hardware_concurrency_is_lock_free.c b/tests/wasm_worker/hardware_concurrency_is_lock_free.c new file mode 100644 index 000000000000..c7e47fcd61b4 --- /dev/null +++ b/tests/wasm_worker/hardware_concurrency_is_lock_free.c @@ -0,0 +1,35 @@ +#include +#include +#include + +// Test emscripten_navigator_hardware_concurrency() and emscripten_atomics_is_lock_free() functions + +void test() +{ + // Assume that test suite does have navigator.hardwareConcurrency. + assert(emscripten_navigator_hardware_concurrency() >= 2); + assert(emscripten_atomics_is_lock_free(1)); + assert(emscripten_atomics_is_lock_free(2)); + assert(emscripten_atomics_is_lock_free(4)); + // Chrome is buggy, see + // https://bugs.chromium.org/p/chromium/issues/detail?id=1167449 + //assert(emscripten_atomics_is_lock_free(8)); + assert(!emscripten_atomics_is_lock_free(31)); +} + +void worker_main() +{ + test(); +#ifdef REPORT_RESULT + REPORT_RESULT(0); +#endif +} + +char stack[1024]; + +int main() +{ + test(); + emscripten_wasm_worker_t worker = emscripten_create_wasm_worker(stack, sizeof(stack)); + emscripten_wasm_worker_post_function_v(worker, worker_main); +} diff --git a/tests/wasm_worker/hello_wasm_worker.c b/tests/wasm_worker/hello_wasm_worker.c new file mode 100644 index 000000000000..ac7f97eb8876 --- /dev/null +++ b/tests/wasm_worker/hello_wasm_worker.c @@ -0,0 +1,27 @@ +#include +#include +#include + +// Test emscripten_create_wasm_worker() and emscripten_current_thread_is_wasm_worker() functions + +EM_JS(void, console_log, (char* str), { + console.log(UTF8ToString(str)); +}); + +void worker_main() +{ + console_log("Hello from wasm worker!"); + assert(emscripten_current_thread_is_wasm_worker()); +#ifdef REPORT_RESULT + REPORT_RESULT(0); +#endif +} + +char stack[1024]; + +int main() +{ + assert(!emscripten_current_thread_is_wasm_worker()); + emscripten_wasm_worker_t worker = emscripten_create_wasm_worker(stack, sizeof(stack)); + emscripten_wasm_worker_post_function_v(worker, worker_main); +} diff --git a/tests/wasm_worker/lock_async_acquire.c b/tests/wasm_worker/lock_async_acquire.c new file mode 100644 index 000000000000..4533a5ecc113 --- /dev/null +++ b/tests/wasm_worker/lock_async_acquire.c @@ -0,0 +1,108 @@ +#include +#include +#include +#include +#include + +// Tests emscripten_lock_async_acquire() function. + +EM_JS(void, console_log, (char* str), { + console.log(UTF8ToString(str)); +}); + +emscripten_lock_t lock = EMSCRIPTEN_LOCK_T_STATIC_INITIALIZER; + +// Two shared variables, always a delta distance of one from each other. +volatile int sharedState0 = 0; +volatile int sharedState1 = 1; + +EM_BOOL testFinished = EM_FALSE; + +int numTimesMainThreadAcquiredLock = 0; +int numTimesWasmWorkerAcquiredLock = 0; + +void work() +{ +// console_log("work"); + volatile int x = sharedState0; + volatile int y = sharedState1; + assert(x == y+1 || y == x+1); + + if (emscripten_current_thread_is_wasm_worker()) + ++numTimesWasmWorkerAcquiredLock; + else + ++numTimesMainThreadAcquiredLock; + + if (x < y) + { + x = y + 1; + if (emscripten_current_thread_is_wasm_worker()) + emscripten_wasm_worker_sleep(/*nsecs=*/(rand()%100000)); + sharedState0 = x; + } + else + { + y = x + 1; + if (emscripten_current_thread_is_wasm_worker()) + emscripten_wasm_worker_sleep(/*nsecs=*/(rand()%100000)); + sharedState1 = y; + + if (y > 100 && numTimesMainThreadAcquiredLock && numTimesWasmWorkerAcquiredLock) + { + if (!testFinished) + { + console_log("test finished"); +#ifdef REPORT_RESULT + REPORT_RESULT(0); +#endif + } + testFinished = EM_TRUE; + } + } +} + +void schedule_work(void *userData); + +void lock_async_acquired(volatile void *addr, uint32_t val, ATOMICS_WAIT_RESULT_T waitResult, void *userData) +{ +// console_log("async lock acquired"); + assert(addr == &lock); + assert(val == 0 || val == 1); + assert(waitResult == ATOMICS_WAIT_OK); + assert(userData == (void*)42); + work(); + emscripten_lock_release(&lock); + + if (!testFinished) + emscripten_set_timeout(schedule_work, 10, 0); +} + +void schedule_work(void *userData) +{ + if (emscripten_current_thread_is_wasm_worker() && emscripten_random() > 0.5) + { + emscripten_lock_waitinf_acquire(&lock); +// console_log("sync lock acquired"); + work(); + emscripten_lock_release(&lock); + if (!testFinished) + emscripten_set_timeout(schedule_work, 0, 0); + } + else + { + emscripten_lock_async_acquire(&lock, lock_async_acquired, (void*)42, EMSCRIPTEN_WAIT_ASYNC_INFINITY); + } +} + +int main() +{ +#define NUM_THREADS 10 + for(int i = 0; i < NUM_THREADS; ++i) + { + void *stack = malloc(1024); + emscripten_wasm_worker_t worker = emscripten_create_wasm_worker(stack, 1024); + emscripten_wasm_worker_post_function_vi(worker, (void (*)(int))schedule_work, 0); + } + + schedule_work(0); +} diff --git a/tests/wasm_worker/lock_busyspin_wait_acquire.c b/tests/wasm_worker/lock_busyspin_wait_acquire.c new file mode 100644 index 000000000000..292f5666b732 --- /dev/null +++ b/tests/wasm_worker/lock_busyspin_wait_acquire.c @@ -0,0 +1,50 @@ +#include +#include +#include +#include +#include + +// Tests emscripten_lock_busyspin_wait_acquire(). + +emscripten_lock_t lock = EMSCRIPTEN_LOCK_T_STATIC_INITIALIZER; + +void test() +{ + EM_BOOL success = emscripten_lock_busyspin_wait_acquire(&lock, 0); // Expect no contention on free lock. + assert(success == EM_TRUE); + + double t0 = emscripten_performance_now(); + success = emscripten_lock_busyspin_wait_acquire(&lock, 0); // We already have the lock, and emscripten_lock is not recursive, so this should fail. + double t1 = emscripten_performance_now(); + assert(!success); + assert(t1 - t0 < 25); // Shouldn't have taken too much time to try the lock. + + success = emscripten_lock_try_acquire(&lock); + assert(!success); // We already have the lock. + + t0 = emscripten_performance_now(); + success = emscripten_lock_busyspin_wait_acquire(&lock, 1000.0); // We already have the lock, and emscripten_lock is not recursive, so this should fail. + t1 = emscripten_performance_now(); + assert(!success); + assert(t1 - t0 >= 1000); // We should have waited for the requested duration for the lock. + + emscripten_lock_release(&lock); +} + +void worker_main() +{ + test(); +#ifdef REPORT_RESULT + REPORT_RESULT(0); +#endif +} + +char stack[1024]; + +int main() +{ + test(); + + emscripten_wasm_worker_t worker = emscripten_create_wasm_worker(stack, sizeof(stack)); + emscripten_wasm_worker_post_function_v(worker, worker_main); +} diff --git a/tests/wasm_worker/lock_busyspin_waitinf_acquire.c b/tests/wasm_worker/lock_busyspin_waitinf_acquire.c new file mode 100644 index 000000000000..f58c2d6e93fa --- /dev/null +++ b/tests/wasm_worker/lock_busyspin_waitinf_acquire.c @@ -0,0 +1,42 @@ +#include +#include +#include +#include +#include + +// Tests emscripten_lock_busyspin_waitinf_acquire(). + +emscripten_lock_t lock = EMSCRIPTEN_LOCK_T_STATIC_INITIALIZER; + +volatile int sharedState = 0; + +void worker_main() +{ + emscripten_lock_busyspin_waitinf_acquire(&lock); + emscripten_atomic_add_u32((void*)&sharedState, 1); +#ifdef REPORT_RESULT + REPORT_RESULT(sharedState); +#endif +} + +char stack[1024]; + +void releaseLock(void *userData) +{ + emscripten_atomic_sub_u32((void*)&sharedState, 1); + emscripten_lock_release(&lock); +} + +int main() +{ + // Acquire the lock at startup. + emscripten_lock_busyspin_waitinf_acquire(&lock); + emscripten_atomic_add_u32((void*)&sharedState, 1); + + // Spawn a Worker to try to take the lock. It will succeed only after releaseLock() + // gets called. + emscripten_wasm_worker_t worker = emscripten_create_wasm_worker(stack, sizeof(stack)); + emscripten_wasm_worker_post_function_v(worker, worker_main); + + emscripten_set_timeout(releaseLock, 1000, 0); +} diff --git a/tests/wasm_worker/lock_wait_acquire.c b/tests/wasm_worker/lock_wait_acquire.c new file mode 100644 index 000000000000..82b8363ca607 --- /dev/null +++ b/tests/wasm_worker/lock_wait_acquire.c @@ -0,0 +1,47 @@ +#include +#include +#include +#include +#include + +// Tests emscripten_lock_wait_acquire() and emscripten_lock_try_acquire() in Worker. + +emscripten_lock_t lock = EMSCRIPTEN_LOCK_T_STATIC_INITIALIZER; + +void worker_main() +{ + EM_BOOL success = emscripten_lock_wait_acquire(&lock, 0); // Expect no contention on free lock. + assert(success == EM_TRUE); + + double t0 = emscripten_performance_now(); + success = emscripten_lock_wait_acquire(&lock, 0); // We already have the lock, and emscripten_lock is not recursive, so this should fail. + double t1 = emscripten_performance_now(); + assert(!success); + assert(t1 - t0 < 25); // Shouldn't have taken too much time to try the lock. + + success = emscripten_lock_try_acquire(&lock); + assert(!success); // We already have the lock. + + t0 = emscripten_performance_now(); + success = emscripten_lock_wait_acquire(&lock, 1000 * 1000000ull); // We already have the lock, and emscripten_lock is not recursive, so this should fail. + t1 = emscripten_performance_now(); + assert(!success); + assert(t1 - t0 >= 1000); // We should have waited for the requested duration for the lock. + + emscripten_lock_release(&lock); + + success = emscripten_lock_try_acquire(&lock); + assert(success); + +#ifdef REPORT_RESULT + REPORT_RESULT(0); +#endif +} + +char stack[1024]; + +int main() +{ + emscripten_wasm_worker_t worker = emscripten_create_wasm_worker(stack, sizeof(stack)); + emscripten_wasm_worker_post_function_v(worker, worker_main); +} diff --git a/tests/wasm_worker/lock_wait_acquire2.c b/tests/wasm_worker/lock_wait_acquire2.c new file mode 100644 index 000000000000..a4833a78f9b1 --- /dev/null +++ b/tests/wasm_worker/lock_wait_acquire2.c @@ -0,0 +1,57 @@ +#include +#include +#include +#include +#include + +// Tests emscripten_lock_wait_acquire() between two Wasm Workers. + +EM_JS(void, console_log, (char* str), { + console.log(UTF8ToString(str)); +}); + +emscripten_lock_t lock = EMSCRIPTEN_LOCK_T_STATIC_INITIALIZER; + +void worker1_main() +{ + console_log("worker1 main try_acquiring lock"); + EM_BOOL success = emscripten_lock_try_acquire(&lock); // Expect no contention on free lock. + console_log("worker1 try_acquire lock finished"); + assert(success); + console_log("worker1 try_acquire lock success, sleeping 1000 msecs"); + emscripten_wasm_worker_sleep(1000 * 1000000ull); + + console_log("worker1 slept 1000 msecs, releasing lock"); + emscripten_lock_release(&lock); + console_log("worker1 released lock"); +} + +void worker2_main() +{ + console_log("worker2 main sleeping 500 msecs"); + emscripten_wasm_worker_sleep(500 * 1000000ull); + console_log("worker2 slept 500 msecs, try_acquiring lock"); + EM_BOOL success = emscripten_lock_try_acquire(&lock); // At this time, the other thread should have the lock. + console_log("worker2 try_acquire lock finished"); + assert(!success); + + // Wait enough time to cover over the time that the other thread held the lock. + success = emscripten_lock_wait_acquire(&lock, 2000 * 1000000ull); + console_log("worker2 wait_acquired lock"); + assert(success); + +#ifdef REPORT_RESULT + REPORT_RESULT(0); +#endif +} + +char stack1[1024]; +char stack2[1024]; + +int main() +{ + emscripten_wasm_worker_t worker1 = emscripten_create_wasm_worker(stack1, sizeof(stack1)); + emscripten_wasm_worker_t worker2 = emscripten_create_wasm_worker(stack2, sizeof(stack2)); + emscripten_wasm_worker_post_function_v(worker1, worker1_main); + emscripten_wasm_worker_post_function_v(worker2, worker2_main); +} diff --git a/tests/wasm_worker/lock_waitinf_acquire.c b/tests/wasm_worker/lock_waitinf_acquire.c new file mode 100644 index 000000000000..cdc8b71ae738 --- /dev/null +++ b/tests/wasm_worker/lock_waitinf_acquire.c @@ -0,0 +1,75 @@ +#include +#include +#include +#include +#include +#include + +// Tests emscripten_lock_init(), emscripten_lock_waitinf_acquire() and emscripten_lock_release() + +emscripten_lock_t lock = (emscripten_lock_t)12345315; // initialize with garbage + +// Two shared variables, always a delta distance of one from each other. +volatile int sharedState0 = 0; +volatile int sharedState1 = 1; + +volatile int numWorkersAlive = 0; + +void test_ended() +{ + EM_ASM(console.log(`Worker ${$0} last thread to finish. Reporting test end with sharedState0=${$1}, sharedState1=${$2}`), emscripten_wasm_worker_self_id(), sharedState0, sharedState1); + assert(sharedState0 == sharedState1 + 1 || sharedState1 == sharedState0 + 1); +#ifdef REPORT_RESULT + REPORT_RESULT(sharedState0); +#endif +} + +void worker_main() +{ + EM_ASM(console.log(`Worker ${$0} running...`), emscripten_wasm_worker_self_id()); + // Create contention on the lock from each thread, and stress the shared state + // in a racy way that would show a breakage if the lock is not watertight. + for(int i = 0; i < 1000; ++i) + { + emscripten_lock_waitinf_acquire(&lock); + volatile int x = sharedState0; + volatile int y = sharedState1; + assert(x == y+1 || y == x+1); + if (x < y) + { + x = y + 1; + emscripten_wasm_worker_sleep(/*nsecs=*/((uint64_t)(emscripten_math_random()*1000))); + sharedState0 = x; + } + else + { + y = x + 1; + emscripten_wasm_worker_sleep(/*nsecs=*/((uint64_t)(emscripten_math_random()*1000))); + sharedState1 = y; + } + emscripten_lock_release(&lock); + } + + EM_ASM(console.log(`Worker ${$0} finished.`), emscripten_wasm_worker_self_id()); + + // Are we the last thread to finish? If so, test has ended. + uint32_t v = emscripten_atomic_sub_u32((void*)&numWorkersAlive, 1); + if (v == 1) + { + test_ended(); + } +} + +int main() +{ + emscripten_lock_init(&lock); + +#define NUM_THREADS 4 + numWorkersAlive = NUM_THREADS; + for(int i = 0; i < NUM_THREADS; ++i) + { + void *stack = malloc(1024); + emscripten_wasm_worker_t worker = emscripten_create_wasm_worker(stack, 1024); + emscripten_wasm_worker_post_function_v(worker, worker_main); + } +} diff --git a/tests/wasm_worker/no_proxied_js_functions.c b/tests/wasm_worker/no_proxied_js_functions.c new file mode 100644 index 000000000000..c6282301b3da --- /dev/null +++ b/tests/wasm_worker/no_proxied_js_functions.c @@ -0,0 +1,44 @@ +#include +#include +#include + +// Test that proxied JS functions cannot be called in Wasm Workers in ASSERTIONS builds. + +void proxied_js_function(void); + +int should_throw(void(*func)()) +{ + int threw = EM_ASM_INT({ + try { + dynCall('v', $0); + } catch(e) { + console.error('Threw an exception like expected: ' + e); + return 1; + } + console.error('Function was expected to throw, but did not!'); + return 0; + }, (int)func); + return threw; +} + +void test() +{ + proxied_js_function(); +} + +void worker_main() +{ + assert(should_throw(test)); +#ifdef REPORT_RESULT + REPORT_RESULT(0); +#endif +} + +char stack[1024]; + +int main() +{ + proxied_js_function(); + emscripten_wasm_worker_t worker = emscripten_create_wasm_worker(stack, sizeof(stack)); + emscripten_wasm_worker_post_function_v(worker, worker_main); +} diff --git a/tests/wasm_worker/no_proxied_js_functions.js b/tests/wasm_worker/no_proxied_js_functions.js new file mode 100644 index 000000000000..d2b827d0f62a --- /dev/null +++ b/tests/wasm_worker/no_proxied_js_functions.js @@ -0,0 +1,5 @@ +mergeInto(LibraryManager.library, { + proxied_js_function__proxy: 'sync', + proxied_js_function: function() { + } +}); diff --git a/tests/wasm_worker/post_function.c b/tests/wasm_worker/post_function.c new file mode 100644 index 000000000000..288d3610d966 --- /dev/null +++ b/tests/wasm_worker/post_function.c @@ -0,0 +1,107 @@ +#include +#include +#include + +// Test emscripten_wasm_worker_post_function_*() API + +EM_JS(void, console_log, (char* str), { + console.log(UTF8ToString(str)); +}); + +volatile int success = 0; + +void v() +{ + console_log("v"); + ++success; +} + +void vi(int i) +{ + console_log("vi"); + assert(i == 1); + ++success; +} + +void vii(int i, int j) +{ + console_log("vii"); + assert(i == 2); + assert(j == 3); + ++success; +} + +void viii(int i, int j, int k) +{ + console_log("viii"); + assert(i == 4); + assert(j == 5); + assert(k == 6); + ++success; +} + +void vd(double i) +{ + console_log("vd"); + assert(i == 1.5); + ++success; +} + +void vdd(double i, double j) +{ + console_log("vdd"); + assert(i == 2.5); + assert(j == 3.5); + ++success; +} + +void vddd(double i, double j, double k) +{ + console_log("vddd"); + assert(i == 4.5); + assert(j == 5.5); + assert(k == 6.5); + ++success; +} + +void viiiiiidddddd(int a, int b, int c, int d, int e, int f, double g, double h, double i, double j, double k, double l) +{ + console_log("viiiiiidddddd"); + assert(a == 10); + assert(b == 11); + assert(c == 12); + assert(d == 13); + assert(e == 14); + assert(f == 15); + assert(g == 16.5); + assert(h == 17.5); + assert(i == 18.5); + assert(j == 19.5); + assert(k == 20.5); + assert(l == 21.5); + ++success; +} + +void test_finished() +{ +#ifdef REPORT_RESULT + REPORT_RESULT(success); +#endif +} + +char stack[1024]; + +int main() +{ + assert(!emscripten_current_thread_is_wasm_worker()); + emscripten_wasm_worker_t worker = emscripten_create_wasm_worker(stack, sizeof(stack)); + emscripten_wasm_worker_post_function_v(worker, v); + emscripten_wasm_worker_post_function_vi(worker, vi, 1); + emscripten_wasm_worker_post_function_vii(worker, vii, 2, 3); + emscripten_wasm_worker_post_function_viii(worker, viii, 4, 5, 6); + emscripten_wasm_worker_post_function_vd(worker, vd, 1.5); + emscripten_wasm_worker_post_function_vdd(worker, vdd, 2.5, 3.5); + emscripten_wasm_worker_post_function_vddd(worker, vddd, 4.5, 5.5, 6.5); + emscripten_wasm_worker_post_function_sig(worker, viiiiiidddddd, "iiiiiidddddd", 10, 11, 12, 13, 14, 15, 16.5, 17.5, 18.5, 19.5, 20.5, 21.5); + emscripten_wasm_worker_post_function_v(worker, test_finished); +} diff --git a/tests/wasm_worker/post_function_to_main_thread.c b/tests/wasm_worker/post_function_to_main_thread.c new file mode 100644 index 000000000000..93aa2d3679c0 --- /dev/null +++ b/tests/wasm_worker/post_function_to_main_thread.c @@ -0,0 +1,36 @@ +#include +#include +#include + +// Test emscripten_wasm_worker_post_function_*() API and EMSCRIPTEN_WASM_WORKER_ID_PARENT +// to send a message back from Worker to its parent thread. + +EM_JS(void, console_log, (char* str), { + console.log(UTF8ToString(str)); +}); + +void test_success(int i, double d) +{ + console_log("test_success"); + assert(!emscripten_current_thread_is_wasm_worker()); + assert(i == 10); + assert(d == 0.5); +#ifdef REPORT_RESULT + REPORT_RESULT(i); +#endif +} + +void worker_main() +{ + console_log("worker_main"); + assert(emscripten_current_thread_is_wasm_worker()); + emscripten_wasm_worker_post_function_sig(EMSCRIPTEN_WASM_WORKER_ID_PARENT, test_success, "id", 10, 0.5); +} + +char stack[1024]; + +int main() +{ + emscripten_wasm_worker_t worker = emscripten_create_wasm_worker(stack, sizeof(stack)); + emscripten_wasm_worker_post_function_v(worker, worker_main); +} diff --git a/tests/wasm_worker/semaphore_try_acquire.c b/tests/wasm_worker/semaphore_try_acquire.c new file mode 100644 index 000000000000..76f840e491c3 --- /dev/null +++ b/tests/wasm_worker/semaphore_try_acquire.c @@ -0,0 +1,41 @@ +#include +#include +#include +#include +#include + +// Tests emscripten_semaphore_try_acquire() on the main thread + +EM_JS(void, console_log, (char* str), { + console.log(UTF8ToString(str)); +}); + +emscripten_semaphore_t unavailable = EMSCRIPTEN_SEMAPHORE_T_STATIC_INITIALIZER(0); +emscripten_semaphore_t available = EMSCRIPTEN_SEMAPHORE_T_STATIC_INITIALIZER(1); + +int main() +{ + console_log("try_acquiring unavailable semaphore should fail"); + int idx = emscripten_semaphore_try_acquire(&unavailable, 1); + assert(idx == -1); + + console_log("try_acquiring too many resources from an available semaphore should fail"); + idx = emscripten_semaphore_try_acquire(&available, 2); + assert(idx == -1); + + console_log("try_acquiring a resource from an available semaphore should succeed"); + idx = emscripten_semaphore_try_acquire(&available, 1); + assert(idx == 0); + + console_log("releasing semaphore resources on main thread should succeed"); + idx = emscripten_semaphore_release(&available, 10); + assert(idx == 0); + + console_log("try_acquiring multiple resources from an available semaphore should succeed"); + idx = emscripten_semaphore_try_acquire(&available, 9); + assert(idx == 1); + +#ifdef REPORT_RESULT + REPORT_RESULT(0); +#endif +} diff --git a/tests/wasm_worker/semaphore_waitinf_acquire.c b/tests/wasm_worker/semaphore_waitinf_acquire.c new file mode 100644 index 000000000000..cd4bd96aa786 --- /dev/null +++ b/tests/wasm_worker/semaphore_waitinf_acquire.c @@ -0,0 +1,91 @@ +#include +#include +#include +#include +#include + +// Tests emscripten_semaphore_init(), emscripten_semaphore_waitinf_acquire() and emscripten_semaphore_release() + +EM_JS(void, console_log, (char* str), { + console.log(UTF8ToString(str)); +}); + +emscripten_semaphore_t threadsWaiting = (emscripten_semaphore_t)12345315; // initialize with garbage +emscripten_semaphore_t threadsRunning = EMSCRIPTEN_SEMAPHORE_T_STATIC_INITIALIZER(0); // initialize with static initializer +emscripten_semaphore_t threadsCompleted = EMSCRIPTEN_SEMAPHORE_T_STATIC_INITIALIZER(0); + +int threadCounter = 0; + +void worker_main() +{ + console_log("worker_main"); + + // Increment semaphore to mark that this thread is waiting for a signal from control thread to start. + emscripten_semaphore_release(&threadsWaiting, 1); + + // Acquire thread run semaphore once main thread has given this thread a go signal. + console_log("worker_main: waiting for thread run signal"); + emscripten_semaphore_waitinf_acquire(&threadsRunning, 1); + + // Do heavy computation: + console_log("worker_main: incrementing work"); + emscripten_atomic_add_u32((void*)&threadCounter, 1); + + // Increment semaphore to signal that this thread has finished. + console_log("worker_main: thread completed"); + emscripten_semaphore_release(&threadsCompleted, 1); +} + +void control_thread() +{ + // Wait until we have three threads available to start running. + console_log("control_thread: waiting for three threads to complete loading"); + emscripten_semaphore_waitinf_acquire(&threadsWaiting, 3); + + // Set the three waiting threads to run simultaneously. + assert(threadCounter == 0); + console_log("control_thread: release three threads to run"); + emscripten_semaphore_release(&threadsRunning, 3); + + // Wait until we have 3 threads completed their run. + console_log("control_thread: waiting for three threads to complete"); + emscripten_semaphore_waitinf_acquire(&threadsCompleted, 3); + assert(threadCounter == 3); + + // Wait until we have next 3 threads available to start running. + console_log("control_thread: waiting for next three threads to be ready"); + emscripten_semaphore_waitinf_acquire(&threadsWaiting, 3); + + // Set the three waiting threads to run simultaneously. + assert(threadCounter == 3); + console_log("control_thread: setting next three threads go"); + emscripten_semaphore_release(&threadsRunning, 3); + + // Wait until we have the final 3 threads completed their run. + console_log("control_thread: waiting for the last three threads to finish"); + emscripten_semaphore_waitinf_acquire(&threadsCompleted, 3); + console_log("control_thread: threads finished"); + assert(threadCounter == 6); + + console_log("control_thread: test finished"); +#ifdef REPORT_RESULT + REPORT_RESULT(0); +#endif +} + +int main() +{ + emscripten_semaphore_init(&threadsWaiting, 0); + + void *stack = malloc(1024); + emscripten_wasm_worker_t worker = emscripten_create_wasm_worker(stack, 1024); + emscripten_wasm_worker_post_function_v(worker, control_thread); + +#define NUM_THREADS 6 + for(int i = 0; i < NUM_THREADS; ++i) + { + void *stack = malloc(1024); + emscripten_wasm_worker_t worker = emscripten_create_wasm_worker(stack, 1024); + emscripten_wasm_worker_post_function_v(worker, worker_main); + } +} diff --git a/tests/wasm_worker/terminate_all_wasm_workers.c b/tests/wasm_worker/terminate_all_wasm_workers.c new file mode 100644 index 000000000000..385a5e31a1b4 --- /dev/null +++ b/tests/wasm_worker/terminate_all_wasm_workers.c @@ -0,0 +1,88 @@ +#include +#include +#include + +// Tests that calling emscripten_terminate_all_wasm_workers() properly terminates +// each child Wasm Worker of the calling thread. + +EM_JS(void, console_error, (char* str), { + console.error(UTF8ToString(str)); +}); + +static volatile int worker_started = 0; + +void this_function_should_not_be_called(void *userData) +{ + worker_started = -1; + console_error("this_function_should_not_be_called"); +#ifdef REPORT_RESULT + REPORT_RESULT(1/*fail*/); +#endif +} + +void test_passed(void *userData) +{ + if (worker_started == 2) + { + console_error("test_passed"); +#ifdef REPORT_RESULT + REPORT_RESULT(0/*ok*/); +#endif + } +} + +void worker_main() +{ + ++worker_started; + console_error("Hello from wasm worker!"); + // Schedule a function to be called, that should never happen, since the Worker + // dies before that. + emscripten_set_timeout(this_function_should_not_be_called, 2000, 0); +} + +char stack1[1024]; +char stack2[1024]; + +int should_throw(void(*func)(emscripten_wasm_worker_t worker), emscripten_wasm_worker_t worker) +{ + int threw = EM_ASM_INT({ + try { + dynCall('vi', $0, $1); + } catch(e) { + console.error('Threw an exception like expected: ' + e); + return 1; + } + console.error('Function was expected to throw, but did not!'); + return 0; + }, (int)func, worker); + return threw; +} + +emscripten_wasm_worker_t worker[2]; + +void post_bad_function(emscripten_wasm_worker_t worker) +{ + // Try to post a function to the worker, this should throw + emscripten_wasm_worker_post_function_vi(worker, (void(*)(int))this_function_should_not_be_called, 0); +} + +void terminate_worker(void *userData) +{ + emscripten_terminate_all_wasm_workers(); + assert(should_throw(post_bad_function, worker[0])); + assert(should_throw(post_bad_function, worker[1])); +} + +int main() +{ + worker[0] = emscripten_create_wasm_worker(stack1, sizeof(stack1)); + worker[1] = emscripten_create_wasm_worker(stack2, sizeof(stack2)); + emscripten_wasm_worker_post_function_v(worker[0], worker_main); + emscripten_wasm_worker_post_function_v(worker[1], worker_main); + + // Terminate both workers after a small delay + emscripten_set_timeout(terminate_worker, 1000, 0); + + // Wait a while, if the bad function does not trigger, then the test succeeds. + emscripten_set_timeout(test_passed, 3000, 0); +} diff --git a/tests/wasm_worker/terminate_wasm_worker.c b/tests/wasm_worker/terminate_wasm_worker.c new file mode 100644 index 000000000000..ce3989bced61 --- /dev/null +++ b/tests/wasm_worker/terminate_wasm_worker.c @@ -0,0 +1,84 @@ +#include +#include +#include + +// Tests that calling emscripten_terminate_wasm_worker() properly terminates +// a Wasm Worker. + +EM_JS(void, console_error, (char* str), { + console.error(UTF8ToString(str)); +}); + +static volatile int worker_started = 0; + +void this_function_should_not_be_called(void *userData) +{ + worker_started = -1; + console_error("this_function_should_not_be_called"); +#ifdef REPORT_RESULT + REPORT_RESULT(1/*fail*/); +#endif +} + +void test_passed(void *userData) +{ + if (worker_started == 1) + { + console_error("test_passed"); +#ifdef REPORT_RESULT + REPORT_RESULT(0/*ok*/); +#endif + } +} + +void worker_main() +{ + worker_started = 1; + console_error("Hello from wasm worker!"); + // Schedule a function to be called, that should never happen, since the Worker + // dies before that. + emscripten_set_timeout(this_function_should_not_be_called, 2000, 0); +} + +char stack[1024]; + +int should_throw(void(*func)()) +{ + int threw = EM_ASM_INT({ + try { + dynCall('v', $0); + } catch(e) { + console.error('Threw an exception like expected: ' + e); + return 1; + } + console.error('Function was expected to throw, but did not!'); + return 0; + }, (int)func); + return threw; +} + +emscripten_wasm_worker_t worker = 0; + +void post_bad_function() +{ + // Try to post a function to the worker, this should throw + emscripten_wasm_worker_post_function_vi(worker, (void(*)(int))this_function_should_not_be_called, 0); +} + +void terminate_worker(void *userData) +{ + emscripten_terminate_wasm_worker(worker); + assert(should_throw(post_bad_function)); +} + +int main() +{ + worker = emscripten_create_wasm_worker(stack, sizeof(stack)); + emscripten_wasm_worker_post_function_v(worker, worker_main); + + // Terminate the worker after a small delay + emscripten_set_timeout(terminate_worker, 1000, 0); + + // Wait a while, if the bad function does not trigger, then the test succeeds. + emscripten_set_timeout(test_passed, 3000, 0); +} diff --git a/tests/wasm_worker/wait32_notify.c b/tests/wasm_worker/wait32_notify.c new file mode 100644 index 000000000000..ab04be49bac5 --- /dev/null +++ b/tests/wasm_worker/wait32_notify.c @@ -0,0 +1,76 @@ +#include +#include +#include +#include + +// Test emscripten_wasm_wait_i32() and emscripten_wasm_notify() functions. + +EM_JS(void, console_log, (char* str), { + console.log(UTF8ToString(str)); +}); + +volatile int32_t addr = 0; + +void worker_main() +{ + console_log("worker_main"); + emscripten_atomic_store_u32((void*)&addr, 1); + + console_log("Waiting on address with unexpected value should return 'not-equal'"); + ATOMICS_WAIT_RESULT_T ret = emscripten_wasm_wait_i32((int32_t*)&addr, 2, /*timeout=*/-1); + assert(ret == ATOMICS_WAIT_NOT_EQUAL); + + console_log("Waiting on address with unexpected value should return 'not-equal' also if timeout==0"); + ret = emscripten_wasm_wait_i32((int32_t*)&addr, 2, /*timeout=*/0); + assert(ret == ATOMICS_WAIT_NOT_EQUAL); + + console_log("Waiting for 0 nanoseconds should return 'timed-out'"); + ret = emscripten_wasm_wait_i32((int32_t*)&addr, 1, /*timeout=*/0); + assert(ret == ATOMICS_WAIT_TIMED_OUT); + + console_log("Waiting for >0 nanoseconds should return 'timed-out'"); + ret = emscripten_wasm_wait_i32((int32_t*)&addr, 1, /*timeout=*/1000); + assert(ret == ATOMICS_WAIT_TIMED_OUT); + + console_log("Waiting for infinitely long should return 'ok'"); + ret = emscripten_wasm_wait_i32((int32_t*)&addr, 1, /*timeout=*/-1); + assert(ret == ATOMICS_WAIT_OK); + + console_log("Test finished"); + +#ifdef REPORT_RESULT + REPORT_RESULT(addr); +#endif +} + +char stack[1024]; + +EM_BOOL main_loop(double time, void *userData) +{ + if (addr == 1) + { + // Burn one second to make sure worker finishes its test. + console_log("main: seen worker running"); + double t0 = emscripten_performance_now(); + while(emscripten_performance_now() < t0 + 1000); + + // Wake the waiter + console_log("main: waking worker"); + addr = 2; + emscripten_wasm_notify((int32_t*)&addr, 1); + + return EM_FALSE; + } + return EM_TRUE; +} + +int main() +{ + console_log("main: creating worker"); + emscripten_wasm_worker_t worker = emscripten_create_wasm_worker(stack, sizeof(stack)); + console_log("main: posting function"); + emscripten_wasm_worker_post_function_v(worker, worker_main); + + console_log("main: entering timeout loop to wait for wasm worker to run"); + emscripten_set_timeout_loop(main_loop, 1000, 0); +} diff --git a/tests/wasm_worker/wait64_notify.c b/tests/wasm_worker/wait64_notify.c new file mode 100644 index 000000000000..9c903978dc74 --- /dev/null +++ b/tests/wasm_worker/wait64_notify.c @@ -0,0 +1,76 @@ +#include +#include +#include +#include + +// Test emscripten_wasm_wait_i64() and emscripten_wasm_notify() functions. + +EM_JS(void, console_log, (char* str), { + console.log(UTF8ToString(str)); +}); + +volatile int64_t addr = 0; + +void worker_main() +{ + console_log("worker_main"); + emscripten_atomic_store_u64((void*)&addr, 0x100000000ull); + + console_log("Waiting on address with unexpected value should return 'not-equal'"); + ATOMICS_WAIT_RESULT_T ret = emscripten_wasm_wait_i64((int64_t*)&addr, 0x200000000ull, /*timeout=*/-1); + assert(ret == ATOMICS_WAIT_NOT_EQUAL); + + console_log("Waiting on address with unexpected value should return 'not-equal' also if timeout==0"); + ret = emscripten_wasm_wait_i64((int64_t*)&addr, 0x200000000ull, /*timeout=*/0); + assert(ret == ATOMICS_WAIT_NOT_EQUAL); + + console_log("Waiting for 0 nanoseconds should return 'timed-out'"); + ret = emscripten_wasm_wait_i64((int64_t*)&addr, 0x100000000ull, /*timeout=*/0); + assert(ret == ATOMICS_WAIT_TIMED_OUT); + + console_log("Waiting for >0 nanoseconds should return 'timed-out'"); + ret = emscripten_wasm_wait_i64((int64_t*)&addr, 0x100000000ull, /*timeout=*/1000); + assert(ret == ATOMICS_WAIT_TIMED_OUT); + + console_log("Waiting for infinitely long should return 'ok'"); + ret = emscripten_wasm_wait_i64((int64_t*)&addr, 0x100000000ull, /*timeout=*/-1); + assert(ret == ATOMICS_WAIT_OK); + + console_log("Test finished"); + +#ifdef REPORT_RESULT + REPORT_RESULT(addr >> 32); +#endif +} + +char stack[1024]; + +EM_BOOL main_loop(double time, void *userData) +{ + if (addr == 0x100000000ull) + { + // Burn one second to make sure worker finishes its test. + console_log("main: seen worker running"); + double t0 = emscripten_performance_now(); + while(emscripten_performance_now() < t0 + 1000); + + // Wake the waiter + console_log("main: waking worker"); + addr = 0x200000000ull; + emscripten_wasm_notify((int32_t*)&addr, 1); + + return EM_FALSE; + } + return EM_TRUE; +} + +int main() +{ + console_log("main: creating worker"); + emscripten_wasm_worker_t worker = emscripten_create_wasm_worker(stack, sizeof(stack)); + console_log("main: posting function"); + emscripten_wasm_worker_post_function_v(worker, worker_main); + + console_log("main: entering timeout loop to wait for wasm worker to run"); + emscripten_set_timeout_loop(main_loop, 1000, 0); +} diff --git a/tests/wasm_worker/wait_async.c b/tests/wasm_worker/wait_async.c new file mode 100644 index 000000000000..9ad0fe1fc621 --- /dev/null +++ b/tests/wasm_worker/wait_async.c @@ -0,0 +1,87 @@ +#include +#include +#include +#include + +// Test emscripten_wasm_wait_i64() and emscripten_wasm_notify() functions. + +EM_JS(void, console_log, (char* str), { + console.log(UTF8ToString(str)); +}); + +volatile int32_t addr = 0; + +void worker_main() +{ + console_log("worker_main"); + emscripten_wasm_worker_sleep(1000 * 1000000ull); // Sleep one second. + console_log("worker: addr = 1234"); + emscripten_atomic_store_u32((void*)&addr, 1234); + console_log("worker: notify async waiting main thread"); + emscripten_wasm_notify((int32_t*)&addr, 1); +} + +char stack[1024]; + +int numCalled = 0; + +void asyncWaitShouldTimeout(int32_t *ptr, uint32_t val, ATOMICS_WAIT_RESULT_T waitResult, void *userData) +{ + console_log("main: asyncWaitShouldTimeout"); + ++numCalled; + assert(numCalled == 1); + assert(ptr == &addr); + assert(val == 1); + assert(userData == (void*)42); + assert(waitResult == ATOMICS_WAIT_TIMED_OUT); +} + +void asyncWaitFinishedShouldNotBeCalled(int32_t *ptr, uint32_t val, ATOMICS_WAIT_RESULT_T waitResult, void *userData) +{ + assert(0); // We should not reach here +} + +void asyncWaitFinishedShouldBeOk(int32_t *ptr, uint32_t val, ATOMICS_WAIT_RESULT_T waitResult, void *userData) +{ + console_log("main: asyncWaitFinished"); + assert(ptr == &addr); + assert(val == 1); + assert(userData == (void*)42); + ++numCalled; + assert(numCalled == 2); + assert(waitResult == ATOMICS_WAIT_OK); + console_log("test finished"); +#ifdef REPORT_RESULT + REPORT_RESULT(0); +#endif +} + +int main() +{ + console_log("main: creating worker"); + emscripten_wasm_worker_t worker = emscripten_create_wasm_worker(stack, sizeof(stack)); + console_log("main: posting function"); + emscripten_wasm_worker_post_function_v(worker, worker_main); + + addr = 1; + + console_log("Async waiting on address with unexpected value should return 'not-equal'"); + ATOMICS_WAIT_TOKEN_T ret = emscripten_atomic_wait_async((int32_t*)&addr, 2, asyncWaitFinishedShouldNotBeCalled, (void*)42, EMSCRIPTEN_WAIT_ASYNC_INFINITY); + assert(ret == ATOMICS_WAIT_NOT_EQUAL); + + console_log("Waiting on address with unexpected value should return 'not-equal' also if timeout==0"); + ret = emscripten_atomic_wait_async((int32_t*)&addr, 2, asyncWaitFinishedShouldNotBeCalled, (void*)42, 0); + assert(ret == ATOMICS_WAIT_NOT_EQUAL); + + console_log("Waiting for 0 milliseconds should return 'timed-out'"); + ret = emscripten_atomic_wait_async((int32_t*)&addr, 1, asyncWaitFinishedShouldNotBeCalled, (void*)42, 0); + assert(ret == ATOMICS_WAIT_TIMED_OUT); + + console_log("Waiting for >0 milliseconds should return 'ok' (but successfully time out in first call to asyncWaitFinished)"); + ret = emscripten_atomic_wait_async((int32_t*)&addr, 1, asyncWaitShouldTimeout, (void*)42, 10); + assert(EMSCRIPTEN_IS_VALID_WAIT_TOKEN(ret)); + + console_log("Waiting for infinitely long should return 'ok' (and return 'ok' in second call to asyncWaitFinished)"); + ret = emscripten_atomic_wait_async((int32_t*)&addr, 1, asyncWaitFinishedShouldBeOk, (void*)42, EMSCRIPTEN_WAIT_ASYNC_INFINITY); + assert(EMSCRIPTEN_IS_VALID_WAIT_TOKEN(ret)); +} diff --git a/tests/wasm_worker/wasm_worker_self_id.c b/tests/wasm_worker/wasm_worker_self_id.c new file mode 100644 index 000000000000..ade1232242c5 --- /dev/null +++ b/tests/wasm_worker/wasm_worker_self_id.c @@ -0,0 +1,52 @@ +#include +#include +#include + +// Test the function emscripten_wasm_worker_self_id() + +EM_JS(void, console_log, (char* str), { + console.log(UTF8ToString(str)); +}); + +emscripten_wasm_worker_t worker1 = 0; +emscripten_wasm_worker_t worker2 = 0; + +int successes = 0; + +void test_success() +{ +#ifdef REPORT_RESULT + if (__atomic_add_fetch(&successes, 1, __ATOMIC_SEQ_CST) == 2) + { + REPORT_RESULT(0); + } +#endif +} + +void worker1_main() +{ + assert(emscripten_wasm_worker_self_id() != 0); + assert(emscripten_wasm_worker_self_id() == worker1); + if (emscripten_wasm_worker_self_id() == worker1) + test_success(); +} + +void worker2_main() +{ + assert(emscripten_wasm_worker_self_id() != 0); + assert(emscripten_wasm_worker_self_id() == worker2); + if (emscripten_wasm_worker_self_id() == worker2) + test_success(); +} + +char stack1[1024]; +char stack2[1024]; + +int main() +{ + assert(emscripten_wasm_worker_self_id() == 0); + worker1 = emscripten_create_wasm_worker(stack1, sizeof(stack1)); + worker2 = emscripten_create_wasm_worker(stack2, sizeof(stack2)); + emscripten_wasm_worker_post_function_v(worker1, worker1_main); + emscripten_wasm_worker_post_function_v(worker2, worker2_main); +} diff --git a/tests/wasm_worker/wasm_worker_sleep.c b/tests/wasm_worker/wasm_worker_sleep.c new file mode 100644 index 000000000000..4285389b2e99 --- /dev/null +++ b/tests/wasm_worker/wasm_worker_sleep.c @@ -0,0 +1,24 @@ +#include +#include + +EM_JS(void, console_log, (char* str), { + console.log(UTF8ToString(str)); +}); + +void worker_main() +{ + double t0 = emscripten_performance_now(); + emscripten_wasm_worker_sleep(/*nsecs=*/1500*1000000); + double t1 = emscripten_performance_now(); +#ifdef REPORT_RESULT + REPORT_RESULT(t1-t0 >= 1500); +#endif +} + +char stack[1024]; + +int main() +{ + emscripten_wasm_worker_t worker = emscripten_create_wasm_worker(stack, sizeof(stack)); + emscripten_wasm_worker_post_function_v(worker, worker_main); +} diff --git a/tools/building.py b/tools/building.py index c69f9ec560c4..c56ea32e3d2f 100644 --- a/tools/building.py +++ b/tools/building.py @@ -287,7 +287,7 @@ def lld_flags_for_executable(external_symbols): if settings.IMPORTED_MEMORY: cmd.append('--import-memory') - if settings.USE_PTHREADS: + if settings.SHARED_MEMORY: cmd.append('--shared-memory') if settings.MEMORY64: diff --git a/tools/minimal_runtime_shell.py b/tools/minimal_runtime_shell.py index 419109f90273..73aa5f165343 100644 --- a/tools/minimal_runtime_shell.py +++ b/tools/minimal_runtime_shell.py @@ -62,6 +62,19 @@ def generate_minimal_runtime_load_statement(target_basename): if download_wasm: files_to_load += [download_wasm] + # Download wasm_worker file + if settings.WASM_WORKERS: + if settings.MODULARIZE: + if settings.WASM_WORKERS == 1: + modularize_imports += ['wasmWorker: URL.createObjectURL(new Blob([r[%d]], { type: \'application/javascript\' }))' % len(files_to_load)] + modularize_imports += ['js: js'] + else: + if settings.WASM_WORKERS == 1: + then_statements += ['%s.wasmWorker = URL.createObjectURL(new Blob([r[%d]], { type: \'application/javascript\' }));' % (settings.EXPORT_NAME, len(files_to_load))] + + if download_wasm and settings.WASM_WORKERS == 1: + files_to_load += ["binary('%s')" % (target_basename + '.ww.js')] + if settings.MODULARIZE and settings.USE_PTHREADS: modularize_imports += ["worker: '{{{ PTHREAD_WORKER_FILE }}}'"] @@ -75,7 +88,12 @@ def generate_minimal_runtime_load_statement(target_basename): # Execute compiled output when building with MODULARIZE if settings.MODULARIZE: - then_statements += ['''// Detour the JS code to a separate variable to avoid instantiating with 'r' array as "this" directly to avoid strict ECMAScript/Firefox GC problems that cause a leak, see https://bugzilla.mozilla.org/show_bug.cgi?id=1540101 + + if settings.WASM_WORKERS: + then_statements += ['''// Detour the JS code to a separate variable to avoid instantiating with 'r' array as "this" directly to avoid strict ECMAScript/Firefox GC problems that cause a leak, see https://bugzilla.mozilla.org/show_bug.cgi?id=1540101 + var js = URL.createObjectURL(new Blob([r[0]], { type: \'application/javascript\' }));\n script(js).then(function(c) { c({ %s }); });''' % ',\n '.join(modularize_imports)] + else: + then_statements += ['''// Detour the JS code to a separate variable to avoid instantiating with 'r' array as "this" directly to avoid strict ECMAScript/Firefox GC problems that cause a leak, see https://bugzilla.mozilla.org/show_bug.cgi?id=1540101 var js = r[0];\n js({ %s });''' % ',\n '.join(modularize_imports)] binary_xhr = ''' function binary(url) { // Downloads a binary file and outputs it in the specified callback @@ -129,20 +147,26 @@ def generate_minimal_runtime_load_statement(target_basename): else: return script_xhr + files_to_load[0] + ";" - if not settings.MODULARIZE: + if not settings.MODULARIZE or settings.WASM_WORKERS: # If downloading multiple files like .wasm or .mem, those need to be loaded in # before we can add the main runtime script to the DOM, so convert the main .js # script load from direct script() load to a binary() load so we can still # immediately start the download, but can control when we add the script to the # DOM. - if settings.USE_PTHREADS: + if settings.USE_PTHREADS or settings.WASM_WORKERS: script_load = "script(url)" else: script_load = "script(url).then(() => { URL.revokeObjectURL(url) });" + if settings.WASM_WORKERS: + save_js = '%s.js = ' % settings.EXPORT_NAME + else: + save_js = '' + files_to_load[0] = "binary('%s')" % (target_basename + '.js') - then_statements += ["var url = URL.createObjectURL(new Blob([r[0]], { type: 'application/javascript' }));", - script_load] + if not settings.MODULARIZE: + then_statements += ["var url = %sURL.createObjectURL(new Blob([r[0]], { type: 'application/javascript' }));" % save_js, + script_load] # Add in binary() XHR loader if used: if any("binary(" in s for s in files_to_load + then_statements): diff --git a/tools/settings.py b/tools/settings.py index a2aeb986d259..36b9c8f86427 100644 --- a/tools/settings.py +++ b/tools/settings.py @@ -62,9 +62,11 @@ 'STRICT', 'EMSCRIPTEN_TRACING', 'USE_PTHREADS', + 'SHARED_MEMORY', 'SUPPORT_LONGJMP', 'DEFAULT_TO_CXX', 'WASM_OBJECT_FILES', + 'WASM_WORKERS', # Internal settings used during compilation 'EXCEPTION_CATCHING_ALLOWED', diff --git a/tools/system_libs.py b/tools/system_libs.py index 8b009808f0a1..c38cde10f052 100644 --- a/tools/system_libs.py +++ b/tools/system_libs.py @@ -470,28 +470,38 @@ def get_usable_variations(cls): class MTLibrary(Library): def __init__(self, **kwargs): self.is_mt = kwargs.pop('is_mt') + self.is_ww = kwargs.pop('is_ww') super().__init__(**kwargs) def get_cflags(self): cflags = super().get_cflags() if self.is_mt: cflags += ['-sUSE_PTHREADS'] + if self.is_ww: + cflags += ['-s', 'WASM_WORKERS=1'] return cflags def get_base_name(self): name = super().get_base_name() if self.is_mt: name += '-mt' + if self.is_ww: + name += '-ww' return name @classmethod def vary_on(cls): - return super().vary_on() + ['is_mt'] + return super().vary_on() + ['is_mt', 'is_ww'] @classmethod def get_default_variation(cls, **kwargs): - return super().get_default_variation(is_mt=settings.USE_PTHREADS, **kwargs) + return super().get_default_variation(is_mt=settings.USE_PTHREADS, is_ww=settings.WASM_WORKERS, **kwargs) + @classmethod + def variations(cls): + combos = super(MTLibrary, cls).variations() + # pthreads and Wasm workers are currently not supported together. + return [combo for combo in combos if not combo['is_mt'] or not combo['is_ww']] class DebugLibrary(Library): def __init__(self, **kwargs): @@ -1018,6 +1028,17 @@ def can_use(self): return super(libprintf_long_double, self).can_use() and settings.PRINTF_LONG_DOUBLE +class libwasm_workers(MTLibrary): + name = 'libwasm_workers' + + cflags = ['-Os', '-pthread'] + + def get_files(self): + return files_in_path( + path='system/lib/wasm_worker', + filenames=['library_wasm_worker.c']) + + class libsockets(MuslInternalLibrary, MTLibrary): name = 'libsockets' @@ -1805,6 +1826,9 @@ def add_library(libname): if settings.USE_WEBGPU: add_library('libwebgpu_cpp') + if settings.WASM_WORKERS: + add_library('libwasm_workers') + return libs_to_link From fa21c4686ba83f762f5053e88248d613976b5af9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jukka=20Jyl=C3=A4nki?= Date: Mon, 7 Feb 2022 16:58:23 +0200 Subject: [PATCH 02/65] Add TLS test, ES6ify. --- src/library_wasm_worker.js | 116 +++++++++--------------- system/include/emscripten/wasm_worker.h | 17 +++- tests/report_result.h | 4 + tests/test_browser.py | 6 ++ tests/wasm_worker/wasm_worker_tls.S | 15 +++ tests/wasm_worker/wasm_worker_tls.c | 32 +++++++ 6 files changed, 113 insertions(+), 77 deletions(-) create mode 100644 tests/wasm_worker/wasm_worker_tls.S create mode 100644 tests/wasm_worker/wasm_worker_tls.c diff --git a/src/library_wasm_worker.js b/src/library_wasm_worker.js index 7162251af06e..5bcab07e4980 100644 --- a/src/library_wasm_worker.js +++ b/src/library_wasm_worker.js @@ -16,22 +16,15 @@ mergeInto(LibraryManager.library, { // Executes a wasm function call received via a postMessage. _wasm_worker_runPostMessage: function(e) { - var data = e.data, wasmCall = data['_wsc']; - if (wasmCall) { - var func = wasmTable.get(wasmCall); - if (data['a'] === 0) func(); - else if (data['a'] === 1) func(data['x']); - else if (data['a'] === 2) func(data['x'], data['y']); - else if (data['a'] === 3) func(data['x'], data['y'], data['z']); - else func.apply(null, data['x']); - } + let data = e.data, wasmCall = data['_wsc']; // '_wsc' is short for 'wasm call', trying to use an identifier name that will never conflict with user code + wasmCall && getWasmTableEntry(wasmCall)(...data['x']); }, // src/postamble_minimal.js brings this symbol in to the build, and calls this function. _wasm_worker_initializeRuntime__deps: ['_wasm_worker_delayedMessageQueue', '_wasm_worker_runPostMessage'], _wasm_worker_initializeRuntime: function() { // Establish the stack space for this Wasm Worker: - var stackTop = Module["sb"] + Module["sz"]; + let stackTop = Module["sb"] + Module["sz"]; #if ASSERTIONS assert(stackTop % 16 == 0); assert(Module["sb"] % 16 == 0); @@ -75,7 +68,7 @@ mergeInto(LibraryManager.library, { assert(stackLowestAddress % 16 == 0); assert(stackSize % 16 == 0); #endif - var worker = _wasm_workers[_wasm_workers_id] = new Worker( + let worker = _wasm_workers[_wasm_workers_id] = new Worker( #if WASM_WORKERS == 2 __wasmWorkerBlobUrl #else @@ -96,6 +89,9 @@ mergeInto(LibraryManager.library, { }, emscripten_terminate_wasm_worker: function(id) { +#if ASSERTIONS + assert(id != 0, 'emscripten_terminate_wasm_worker() cannot be called with id=0!'); +#endif if (_wasm_workers[id]) { _wasm_workers[id].terminate(); delete _wasm_workers[id]; @@ -103,6 +99,9 @@ mergeInto(LibraryManager.library, { }, emscripten_terminate_all_wasm_workers: function() { +#if ASSERTIONS + assert(!ENVIRONMENT_IS_WASM_WORKER, 'emscripten_terminate_all_wasm_workers() cannot be called from a Wasm Worker: only the main browser thread has visibility to terminate all Workers!'); +#endif Object.values(_wasm_workers).forEach((worker) => { worker.terminate(); }); @@ -124,22 +123,13 @@ mergeInto(LibraryManager.library, { emscripten_wasm_worker_post_function_v__deps: ['$dynCall'], emscripten_wasm_worker_post_function_v__sig: 'vii', emscripten_wasm_worker_post_function_v: function(id, funcPtr) { - var worker = _wasm_workers[id]; - worker.postMessage({ - '_wsc': funcPtr, // "WaSm Call" - 'a': 0, - }); + _wasm_workers[id].postMessage({'_wsc': funcPtr, 'x': [] }); // "WaSm Call" }, emscripten_wasm_worker_post_function_1__deps: ['$dynCall'], emscripten_wasm_worker_post_function_1__sig: 'viid', emscripten_wasm_worker_post_function_1: function(id, funcPtr, arg0) { - var worker = _wasm_workers[id]; - worker.postMessage({ - '_wsc': funcPtr, // "WaSm Call" - 'a': 1, - 'x': arg0 - }); + _wasm_workers[id].postMessage({'_wsc': funcPtr, 'x': [arg0] }); // "WaSm Call" }, emscripten_wasm_worker_post_function_vi: 'emscripten_wasm_worker_post_function_1', @@ -148,13 +138,7 @@ mergeInto(LibraryManager.library, { emscripten_wasm_worker_post_function_2__deps: ['$dynCall'], emscripten_wasm_worker_post_function_2__sig: 'viidd', emscripten_wasm_worker_post_function_2: function(id, funcPtr, arg0, arg1) { - var worker = _wasm_workers[id]; - worker.postMessage({ - '_wsc': funcPtr, // "WaSm Call" - 'a': 2, - 'x': arg0, - 'y': arg1 - }); + _wasm_workers[id].postMessage({'_wsc': funcPtr, 'x': [arg0, arg1] }); // "WaSm Call" }, emscripten_wasm_worker_post_function_vii: 'emscripten_wasm_worker_post_function_2', emscripten_wasm_worker_post_function_vdd: 'emscripten_wasm_worker_post_function_2', @@ -162,14 +146,7 @@ mergeInto(LibraryManager.library, { emscripten_wasm_worker_post_function_3__deps: ['$dynCall'], emscripten_wasm_worker_post_function_3__sig: 'viiddd', emscripten_wasm_worker_post_function_3: function(id, funcPtr, arg0, arg1, arg2) { - var worker = _wasm_workers[id]; - worker.postMessage({ - '_wsc': funcPtr, // "WaSm Call" - 'a': 3, - 'x': arg0, - 'y': arg1, - 'z': arg2 - }); + _wasm_workers[id].postMessage({'_wsc': funcPtr, 'x': [arg0, arg1, arg2] }); // "WaSm Call" }, emscripten_wasm_worker_post_function_viii: 'emscripten_wasm_worker_post_function_3', emscripten_wasm_worker_post_function_vddd: 'emscripten_wasm_worker_post_function_3', @@ -180,15 +157,10 @@ mergeInto(LibraryManager.library, { assert(id >= 0); assert(funcPtr); assert(sigPtr); - assert(UTF8ToString(sigPtr)[0] != 'v', 'Do NOT specify the return argument in the signature string for a call to emscripten_wasm_worker_post_function_sig(), it is always discarded.'); + assert(UTF8ToString(sigPtr)[0] != 'v', 'Do NOT specify the return argument in the signature string for a call to emscripten_wasm_worker_post_function_sig(), just pass the function arguments.'); assert(varargs); #endif - var worker = _wasm_workers[id]; - worker.postMessage({ - '_wsc': funcPtr, // "WaSm Call" - 'a': -1, - 'x': readAsmConstArgs(sigPtr, varargs) - }); + _wasm_workers[id].postMessage({'_wsc': funcPtr, 'x': readAsmConstArgs(sigPtr, varargs) }); }, _emscripten_atomic_wait_states: "['ok', 'not-equal', 'timed-out']", @@ -204,14 +176,14 @@ mergeInto(LibraryManager.library, { // https://github.com/tc39/proposal-atomics-wait-async/blob/master/PROPOSAL.md // This polyfill performs polling with setTimeout() to observe a change in the target memory location. emscripten_atomic_wait_async__postset: "if (!Atomics['waitAsync'] || navigator.userAgent.indexOf('Chrome') != -1) { \n"+ -"var __Atomics_waitAsyncAddresses = [/*[i32a, index, value, maxWaitMilliseconds, promiseResolve]*/];\n"+ +"let __Atomics_waitAsyncAddresses = [/*[i32a, index, value, maxWaitMilliseconds, promiseResolve]*/];\n"+ "function __Atomics_pollWaitAsyncAddresses() {\n"+ -" var now = performance.now();\n"+ -" var l = __Atomics_waitAsyncAddresses.length;\n"+ -" for(var i = 0; i < l; ++i) {\n"+ -" var a = __Atomics_waitAsyncAddresses[i];\n"+ -" var expired = (now > a[3]);\n"+ -" var awoken = (Atomics.load(a[0], a[1]) != a[2]);\n"+ +" let now = performance.now();\n"+ +" let l = __Atomics_waitAsyncAddresses.length;\n"+ +" for(let i = 0; i < l; ++i) {\n"+ +" let a = __Atomics_waitAsyncAddresses[i];\n"+ +" let expired = (now > a[3]);\n"+ +" let awoken = (Atomics.load(a[0], a[1]) != a[2]);\n"+ " if (expired || awoken) {\n"+ " __Atomics_waitAsyncAddresses[i--] = __Atomics_waitAsyncAddresses[--l];\n"+ " __Atomics_waitAsyncAddresses.length = l;\n"+ @@ -227,12 +199,12 @@ mergeInto(LibraryManager.library, { " if (!ENVIRONMENT_IS_WASM_WORKER) console.error('Current environment does not support Atomics.waitAsync(): polyfilling it, but this is going to be suboptimal.');\n"+ #endif "Atomics['waitAsync'] = function(i32a, index, value, maxWaitMilliseconds) {\n"+ -" var val = Atomics.load(i32a, index);\n"+ +" let val = Atomics.load(i32a, index);\n"+ " if (val != value) return { async: false, value: 'not-equal' };\n"+ " if (maxWaitMilliseconds <= 0) return { async: false, value: 'timed-out' };\n"+ -" var maxWaitMilliseconds = performance.now() + (maxWaitMilliseconds || Infinity);\n"+ -" var promiseResolve;\n"+ -" var promise = new Promise((resolve) => { promiseResolve = resolve; });\n"+ +" maxWaitMilliseconds = performance.now() + (maxWaitMilliseconds || Infinity);\n"+ +" let promiseResolve;\n"+ +" let promise = new Promise((resolve) => { promiseResolve = resolve; });\n"+ " if (!__Atomics_waitAsyncAddresses[0]) setTimeout(__Atomics_pollWaitAsyncAddresses, 10);\n"+ " __Atomics_waitAsyncAddresses.push([i32a, index, value, maxWaitMilliseconds, promiseResolve]);\n"+ " return { async: true, value: promise };\n"+ @@ -252,11 +224,11 @@ mergeInto(LibraryManager.library, { emscripten_atomic_wait_async__deps: ['_emscripten_atomic_wait_states', '_emscripten_atomic_live_wait_asyncs', '_emscripten_atomic_live_wait_asyncs_counter'], emscripten_atomic_wait_async: function(addr, val, asyncWaitFinished, userData, maxWaitMilliseconds) { - var wait = Atomics['waitAsync'](HEAP32, addr >> 2, val, maxWaitMilliseconds); + let wait = Atomics['waitAsync'](HEAP32, addr >> 2, val, maxWaitMilliseconds); if (!wait.async) return __emscripten_atomic_wait_states.indexOf(wait.value); // Increment waitAsync generation counter, account for wraparound in case application does huge amounts of waitAsyncs per second (not sure if possible?) // Valid counterrange: 0...2^31-1 - var counter = __emscripten_atomic_live_wait_asyncs_counter; + let counter = __emscripten_atomic_live_wait_asyncs_counter; __emscripten_atomic_live_wait_asyncs_counter = Math.max(0, (__emscripten_atomic_live_wait_asyncs_counter+1)|0); __emscripten_atomic_live_wait_asyncs[counter] = addr >> 2; wait.value.then((value) => { @@ -290,7 +262,7 @@ mergeInto(LibraryManager.library, { emscripten_atomic_cancel_all_wait_asyncs__deps: ['_emscripten_atomic_live_wait_asyncs'], emscripten_atomic_cancel_all_wait_asyncs: function() { - var waitAsyncs = Object.values(__emscripten_atomic_live_wait_asyncs); + let waitAsyncs = Object.values(__emscripten_atomic_live_wait_asyncs); waitAsyncs.forEach((address) => { Atomics.notify(HEAP32, address); }); @@ -301,7 +273,7 @@ mergeInto(LibraryManager.library, { emscripten_atomic_cancel_all_wait_asyncs_at_address__deps: ['_emscripten_atomic_live_wait_asyncs'], emscripten_atomic_cancel_all_wait_asyncs_at_address: function(address) { address >>= 2; - var numCancelled = 0; + let numCancelled = 0; Object.keys(__emscripten_atomic_live_wait_asyncs).forEach((waitToken) => { if (__emscripten_atomic_live_wait_asyncs[waitToken] == address) { Atomics.notify(HEAP32, address); @@ -324,48 +296,48 @@ mergeInto(LibraryManager.library, { }, emscripten_lock_async_acquire: function(lock, asyncWaitFinished, userData, maxWaitMilliseconds) { - function dispatch(val, ret) { + let dispatch = (val, ret) => { setTimeout(() => { {{{ makeDynCall('viiii', 'asyncWaitFinished') }}}(lock, val, /*waitResult=*/ret, userData); }, 0); - } - function tryAcquireLock() { + }; + let tryAcquireLock = () => { do { - var val = Atomics.compareExchange(HEAPU32, lock >> 2, 0/*zero represents lock being free*/, 1/*one represents lock being acquired*/); + let val = Atomics.compareExchange(HEAPU32, lock >> 2, 0/*zero represents lock being free*/, 1/*one represents lock being acquired*/); if (!val) return dispatch(0, 0/*'ok'*/); - var wait = Atomics['waitAsync'](HEAPU32, lock >> 2, val, maxWaitMilliseconds); + let wait = Atomics['waitAsync'](HEAPU32, lock >> 2, val, maxWaitMilliseconds); } while(wait.value === 'not-equal'); #if ASSERTIONS assert(wait.async || wait.value === 'timed-out'); #endif if (wait.async) wait.value.then(tryAcquireLock); else dispatch(val, 2/*'timed-out'*/); - } + }; tryAcquireLock(); }, emscripten_semaphore_async_acquire: function(sem, num, asyncWaitFinished, userData, maxWaitMilliseconds) { - function dispatch(idx, ret) { + let dispatch = (idx, ret) => { setTimeout(() => { {{{ makeDynCall('viiii', 'asyncWaitFinished') }}}(sem, /*val=*/idx, /*waitResult=*/ret, userData); }, 0); - } - function tryAcquireSemaphore() { - var val = num; + }; + let tryAcquireSemaphore = () => { + let val = num; do { - var ret = Atomics.compareExchange(HEAPU32, sem >> 2, + let ret = Atomics.compareExchange(HEAPU32, sem >> 2, val, /* We expect this many semaphore resoures to be available*/ val - num /* Acquire 'num' of them */); if (ret == val) return dispatch(ret/*index of resource acquired*/, 0/*'ok'*/); val = ret; - var wait = Atomics['waitAsync'](HEAPU32, sem >> 2, ret, maxWaitMilliseconds); + let wait = Atomics['waitAsync'](HEAPU32, sem >> 2, ret, maxWaitMilliseconds); } while(wait.value === 'not-equal'); #if ASSERTIONS assert(wait.async || wait.value === 'timed-out'); #endif if (wait.async) wait.value.then(tryAcquireSemaphore); else dispatch(-1/*idx*/, 2/*'timed-out'*/); - } + }; tryAcquireSemaphore(); } }); diff --git a/system/include/emscripten/wasm_worker.h b/system/include/emscripten/wasm_worker.h index 72aaa16836ff..45726d78829b 100644 --- a/system/include/emscripten/wasm_worker.h +++ b/system/include/emscripten/wasm_worker.h @@ -28,11 +28,18 @@ EM_BOOL emscripten_current_thread_is_wasm_worker(void); // The main browser thread will return 0 as the ID. First Wasm Worker will return 1, and so on. uint32_t emscripten_wasm_worker_self_id(void); -// postMessage()s a function call over to the given Wasm Worker. -// Note that if the Wasm Worker runs in an infinite loop, it will not process the postMessage -// queue to dispatch the function call, until the infinite loop is broken and execution is returned -// back to the Worker event loop. -// Exists, but is a no-op if not building with Wasm Workers enabled (-s WASM_WORKERS=0) +/* emscripten_wasm_worker_post_function_*: Post a pointer to a C/C++ function to be executed on the + target Wasm Worker (via sending a postMessage() to the target thread). Notes: + - The target worker 'id' must have been created by the same thread that is calling this function. That is, + a Wasm Worker cannot send a message to another Worker via this function. + - If running in a Wasm Worker context, specify worker ID 0 to pass a message to the parent thread. + - The target function pointer will be executed on the target Worker only after it yields back to its + event loop. If the target Wasm Worker executes an infinite loop that never yields, then the function + pointer will never be called. + - Passing messages between threads with this family of functions is relatively slow and has a really high + latency cost compared to direct coordination using atomics and synchronization primitives like mutexes + and synchronization primitives. Additionally these functions will generate garbage on the JS heap. + Therefore avoid using these functions where performance is critical. */ void emscripten_wasm_worker_post_function_v(emscripten_wasm_worker_t id, void (*funcPtr)(void)); void emscripten_wasm_worker_post_function_vi(emscripten_wasm_worker_t id, void (*funcPtr)(int), int arg0); void emscripten_wasm_worker_post_function_vii(emscripten_wasm_worker_t id, void (*funcPtr)(int, int), int arg0, int arg1); diff --git a/tests/report_result.h b/tests/report_result.h index 39f2e5c83007..7fc2f19cad07 100644 --- a/tests/report_result.h +++ b/tests/report_result.h @@ -10,6 +10,8 @@ #ifndef REPORT_RESULT_H_ #define REPORT_RESULT_H_ +#ifndef __ASSEMBLER__ // Emit this file only to C/C++ language headers and not when preprocessing .S files + #ifdef __cplusplus extern "C" { #endif @@ -35,4 +37,6 @@ void _MaybeReportResult(int result, int sync); #define MAYBE_REPORT_RESULT_SYNC(result) _MaybeReportResult((result), 1) #endif +#endif + #endif // REPORT_RESULT_H_ diff --git a/tests/test_browser.py b/tests/test_browser.py index d3b78b72a1fe..15d7a710dab4 100644 --- a/tests/test_browser.py +++ b/tests/test_browser.py @@ -5166,6 +5166,12 @@ def test_wasm_worker_self_id(self): expected='0', args=['-s', 'WASM_WORKERS=1', '-s', 'MINIMAL_RUNTIME=1']) + # Tests TLS variables in Wasm Workers + def test_wasm_worker_tls(self): + self.btest(path_from_root('tests', 'wasm_worker', 'wasm_worker_tls.c'), + expected='42', + args=['-s', 'WASM_WORKERS=1', '-s', 'MINIMAL_RUNTIME=1', path_from_root('tests', 'wasm_worker', 'wasm_worker_tls.S')]) + # Tests emscripten_wasm_worker_sleep() def test_wasm_worker_sleep(self): self.btest(path_from_root('tests', 'wasm_worker', 'wasm_worker_sleep.c'), diff --git a/tests/wasm_worker/wasm_worker_tls.S b/tests/wasm_worker/wasm_worker_tls.S new file mode 100644 index 000000000000..cffb0901264e --- /dev/null +++ b/tests/wasm_worker/wasm_worker_tls.S @@ -0,0 +1,15 @@ +.globaltype tlsVariable, i32 +tlsVariable: + +.globl get_tls_variable +get_tls_variable: + .functype get_tls_variable () -> (i32) + global.get tlsVariable + end_function + +.globl set_tls_variable +set_tls_variable: + .functype set_tls_variable (i32) -> () + local.get 0 + global.set tlsVariable + end_function diff --git a/tests/wasm_worker/wasm_worker_tls.c b/tests/wasm_worker/wasm_worker_tls.c new file mode 100644 index 000000000000..77ece77df91a --- /dev/null +++ b/tests/wasm_worker/wasm_worker_tls.c @@ -0,0 +1,32 @@ +#include +#include +#include + +int get_tls_variable(void); +void set_tls_variable(int var); + +void main_thread_func() +{ + assert(!emscripten_current_thread_is_wasm_worker()); +#ifdef REPORT_RESULT + REPORT_RESULT(get_tls_variable()); +#endif +} + +void worker_main() +{ + assert(emscripten_current_thread_is_wasm_worker()); + assert(get_tls_variable() == 0); + set_tls_variable(123456); // Try to write garbage data to the memory location. + emscripten_wasm_worker_post_function_v(0, main_thread_func); +} + +char stack[1024]; + +int main() +{ + assert(!emscripten_current_thread_is_wasm_worker()); + set_tls_variable(42); + emscripten_wasm_worker_t worker = emscripten_create_wasm_worker(stack, sizeof(stack)); + emscripten_wasm_worker_post_function_v(worker, worker_main); +} From f466e620371afcba11667c9cfacbd499f685d340 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jukka=20Jyl=C3=A4nki?= Date: Mon, 7 Feb 2022 17:02:41 +0200 Subject: [PATCH 03/65] Update test --- tests/wasm_worker/wasm_worker_tls.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/tests/wasm_worker/wasm_worker_tls.c b/tests/wasm_worker/wasm_worker_tls.c index 77ece77df91a..fe72228a8c59 100644 --- a/tests/wasm_worker/wasm_worker_tls.c +++ b/tests/wasm_worker/wasm_worker_tls.c @@ -2,12 +2,16 @@ #include #include +// Verify that the global heap is not overrun (reinitialized) on worker creation. +int globalData = 1; + int get_tls_variable(void); void set_tls_variable(int var); void main_thread_func() { assert(!emscripten_current_thread_is_wasm_worker()); + assert(globalData == 3); #ifdef REPORT_RESULT REPORT_RESULT(get_tls_variable()); #endif @@ -17,6 +21,8 @@ void worker_main() { assert(emscripten_current_thread_is_wasm_worker()); assert(get_tls_variable() == 0); + assert(globalData == 2); + globalData = 3; set_tls_variable(123456); // Try to write garbage data to the memory location. emscripten_wasm_worker_post_function_v(0, main_thread_func); } @@ -26,6 +32,8 @@ char stack[1024]; int main() { assert(!emscripten_current_thread_is_wasm_worker()); + assert(globalData == 1); + globalData = 2; set_tls_variable(42); emscripten_wasm_worker_t worker = emscripten_create_wasm_worker(stack, sizeof(stack)); emscripten_wasm_worker_post_function_v(worker, worker_main); From bd1e261c65fc6f6151db67115f50e5d23cd7f3f3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jukka=20Jyl=C3=A4nki?= Date: Tue, 15 Feb 2022 13:25:17 +0200 Subject: [PATCH 04/65] Add TLS support to Wasm Workers --- emcc.py | 6 +- src/library_wasm_worker.js | 45 +++++++---- src/settings.js | 2 + system/include/emscripten/wasm_worker.h | 2 +- system/lib/wasm_worker/library_wasm_worker.c | 81 ++++++++++++++++++-- tools/deps_info.py | 2 + tools/system_libs.py | 31 +++++++- 7 files changed, 145 insertions(+), 24 deletions(-) diff --git a/emcc.py b/emcc.py index 4afcbb9d6fc0..9e4fb85e6e1d 100755 --- a/emcc.py +++ b/emcc.py @@ -2098,9 +2098,11 @@ def phase_linker_setup(options, state, newargs, user_settings): settings.EXPORTED_FUNCTIONS += ['_emscripten_format_exception', '_free'] if settings.WASM_WORKERS: - settings.EXPORTED_FUNCTIONS += ['_emscripten_stack_set_limits'] + # TODO: After #15982 is resolved, these dependencies can be declared in library_wasm_worker.js + # instead of having to record them here. + settings.EXPORTED_FUNCTIONS += ['_emscripten_stack_set_limits', '_emscripten_wasm_worker_initialize'] settings.DEFAULT_LIBRARY_FUNCS_TO_INCLUDE += ['_wasm_worker_initializeRuntime'] - # set location of Wasm Worker bootstrap .js + # set location of Wasm Worker bootstrap JS file if settings.WASM_WORKERS == 1: settings.WASM_WORKER_FILE = unsuffixed(os.path.basename(target)) + '.ww.js' settings.JS_LIBRARIES.append((0, shared.path_from_root('src', 'library_wasm_worker.js'))) diff --git a/src/library_wasm_worker.js b/src/library_wasm_worker.js index 5bcab07e4980..365a14597e73 100644 --- a/src/library_wasm_worker.js +++ b/src/library_wasm_worker.js @@ -20,27 +20,35 @@ mergeInto(LibraryManager.library, { wasmCall && getWasmTableEntry(wasmCall)(...data['x']); }, - // src/postamble_minimal.js brings this symbol in to the build, and calls this function. + // src/postamble_minimal.js brings this symbol in to the build, and calls this function synchronously + // from main JS file at the startup of each Worker. _wasm_worker_initializeRuntime__deps: ['_wasm_worker_delayedMessageQueue', '_wasm_worker_runPostMessage'], _wasm_worker_initializeRuntime: function() { - // Establish the stack space for this Wasm Worker: - let stackTop = Module["sb"] + Module["sz"]; + let m = Module; #if ASSERTIONS - assert(stackTop % 16 == 0); - assert(Module["sb"] % 16 == 0); + assert(m['sb'] % 16 == 0); + assert(m['sz'] % 16 == 0); #endif - // TODO: Fuse these to the same function "emscripten_establish_stack". - _emscripten_stack_set_limits(stackTop, Module["sb"]); - stackRestore(stackTop); + + // Run the C side Worker initialization for stack and TLS. + _emscripten_wasm_worker_initialize(m['sb'], m['sz'] +#if !WASM_WORKERS_NO_TLS + , m['tb'], m['tz'] +#endif + ); + // The above function initializes the stack for this Worker, but C code cannot + // call to extern __set_stack_limits() function, or Binaryen breaks with + // "Fatal: Module::addFunction: __set_stack_limits already exists". + // So for now, invoke the function from JS side. TODO: remove this in the future. #if STACK_OVERFLOW_CHECK >= 2 ___set_stack_limits(_emscripten_stack_get_base(), _emscripten_stack_get_end()); #endif // The Wasm Worker runtime is now up, so we can start processing // any postMessage function calls that have been received. Drop the temp - // message handler that appended incoming postMessage function calls to a queue ... + // message handler that queued any pending incoming postMessage function calls ... removeEventListener('message', __wasm_worker_appendToQueue); - // ... then flush whatever messages we may have gotten in the queue ... + // ... then flush whatever messages we may have already gotten in the queue ... __wasm_worker_delayedMessageQueue.forEach(__wasm_worker_runPostMessage); __wasm_worker_delayedMessageQueue = null; // ... and finally register the proper postMessage handler that immediately @@ -63,10 +71,13 @@ mergeInto(LibraryManager.library, { + '_wasm_workers[0] = this;\n' + 'addEventListener("message", __wasm_worker_appendToQueue);\n' + '}\n', - _emscripten_create_wasm_worker: function(stackLowestAddress, stackSize) { + _emscripten_create_wasm_worker: function(stackLowestAddress, stackSize, tlsAddress, tlsSize) { #if ASSERTIONS assert(stackLowestAddress % 16 == 0); assert(stackSize % 16 == 0); +#if !WASM_WORKERS_NO_TLS + assert(tlsAddress != 0 || tlsSize == 0); +#endif #endif let worker = _wasm_workers[_wasm_workers_id] = new Worker( #if WASM_WORKERS == 2 @@ -81,12 +92,20 @@ mergeInto(LibraryManager.library, { 'wasm': Module['wasm'], 'js': Module['js'], 'mem': wasmMemory, - 'sb': stackLowestAddress, - 'sz': stackSize, + 'sb': stackLowestAddress, // sb = stack base + 'sz': stackSize, // sz = stack size +#if !WASM_WORKERS_NO_TLS + 'tb': tlsAddress, // tb = TLS base + 'tz': tlsSize // tz = TLS size +#endif }); worker.addEventListener('message', __wasm_worker_runPostMessage); return _wasm_workers_id++; }, + _emscripten_create_wasm_worker_no_tls__deps: ['_emscripten_create_wasm_worker'], + _emscripten_create_wasm_worker_no_tls: function(stackLowestAddress, stackSize) { + return __emscripten_create_wasm_worker(stackLowestAddress, stackSize, 0, 0); + }, emscripten_terminate_wasm_worker: function(id) { #if ASSERTIONS diff --git a/src/settings.js b/src/settings.js index 92a2b5456075..76a0dd77b78a 100644 --- a/src/settings.js +++ b/src/settings.js @@ -1475,6 +1475,8 @@ var USE_PTHREADS = 0; // of Wasm SharedArrayBuffer + Atomics API. // [compile+link] - affects user code at compile and system libraries at link. var WASM_WORKERS = 0; +// Wasm Workers options: +var WASM_WORKERS_NO_TLS = 0; // set to 1 to disable TLS for small code size gain when not using TLS. // In web browsers, Workers cannot be created while the main browser thread // is executing JS/Wasm code, but the main thread must regularly yield back diff --git a/system/include/emscripten/wasm_worker.h b/system/include/emscripten/wasm_worker.h index 45726d78829b..769572525246 100644 --- a/system/include/emscripten/wasm_worker.h +++ b/system/include/emscripten/wasm_worker.h @@ -11,7 +11,7 @@ extern "C" { #define EMSCRIPTEN_WASM_WORKER_ID_PARENT 0 // If not building with Wasm workers enabled (-s WASM_WORKERS=0), returns 0. -emscripten_wasm_worker_t emscripten_create_wasm_worker(void *stackLowestAddress, uint32_t stackSize); +emscripten_wasm_worker_t emscripten_create_wasm_worker(void *stackLowestAddress, uint32_t stackSize, void *tlsAddress, uint32_t tlsSize); // Exists, but is a no-op if not building with Wasm Workers enabled (-s WASM_WORKERS=0) void emscripten_terminate_wasm_worker(emscripten_wasm_worker_t id); diff --git a/system/lib/wasm_worker/library_wasm_worker.c b/system/lib/wasm_worker/library_wasm_worker.c index 5b6ddbbbfc18..b99aecc72435 100644 --- a/system/lib/wasm_worker/library_wasm_worker.c +++ b/system/lib/wasm_worker/library_wasm_worker.c @@ -1,15 +1,86 @@ +#include #include #include +#include + +// Options: +// #define WASM_WORKER_NO_TLS 0/1 : set to 1 to disable TLS compilation support for a small code size gain +// #define STACK_OVERFLOW_CHECK 0/1/2 : set to the current stack overflow check mode // Internal implementation function in JavaScript side that emscripten_create_wasm_worker() calls to // to perform the wasm worker creation. -emscripten_wasm_worker_t _emscripten_create_wasm_worker(void *stackLowestAddress, uint32_t stackSize); +emscripten_wasm_worker_t _emscripten_create_wasm_worker(void *stackLowestAddress, uint32_t stackSize, void *tlsAddress, uint32_t tlsSize); +emscripten_wasm_worker_t _emscripten_create_wasm_worker_no_tls(void *stackLowestAddress, uint32_t stackSize); + +void emscripten_stack_set_limits(uint32_t stackLowestAddress, uint32_t stackSize); +void __wasm_init_tls(void *memory); +void stackRestore(uint32_t stackBase); + +#if STACK_OVERFLOW_CHECK == 2 +// TODO: +//void __set_stack_limits(uint32_t stackLowestAddress, uint32_t stackSize); +#endif + +#if !WASM_WORKER_NO_TLS +__attribute__((constructor(48))) +static void emscripten_wasm_worker_main_thread_initialize() { + uintptr_t* sbrk_ptr = emscripten_get_sbrk_ptr(); + __wasm_init_tls((void*)*sbrk_ptr); + *sbrk_ptr += __builtin_wasm_tls_size(); +} +#endif + +// Called in the Wasm Worker thread context to set up the Worker stack space and TLS +void emscripten_wasm_worker_initialize(void *stackLowestAddress, uint32_t stackSize +#if !WASM_WORKER_NO_TLS + , void *tlsAddress, uint32_t tlsSize +#endif + ) { + + assert((uintptr_t)stackLowestAddress % 16 == 0); + assert(stackSize % 16 == 0); + +#if !WASM_WORKER_NO_TLS + assert((uintptr_t)tlsAddress % __builtin_wasm_tls_align() == 0); + assert(tlsSize == __builtin_wasm_tls_size()); + // Set up TLS + __wasm_init_tls(tlsAddress); +#endif + + // Set up stack + uint32_t stackTop = (uint32_t)stackLowestAddress + stackSize; + emscripten_stack_set_limits(stackTop, stackSize); + stackRestore(stackTop); +#if STACK_OVERFLOW_CHECK == 2 + // TODO: Cannot call this function from C code, Binaryen does not want to emit the function then + //__set_stack_limits((uint32_t)stackLowestAddress, stackSize); +#endif +} + +emscripten_wasm_worker_t emscripten_create_wasm_worker(void *stackLowestAddress, uint32_t stackSize, void *tlsAddress, uint32_t tlsSize) +{ +#if WASM_WORKER_NO_TLS + return emscripten_create_wasm_worker_no_tls(stackLowestAddress, stackSize); +#else + assert((uintptr_t)stackLowestAddress % 16 == 0); + assert(stackSize % 16 == 0); + assert((uintptr_t)tlsAddress % __builtin_wasm_tls_align() == 0); + assert(tlsSize == __builtin_wasm_tls_size()); + assert(tlsAddress != 0 || tlsSize == 0); + return _emscripten_create_wasm_worker((void*)stackLowestAddress, stackSize, tlsAddress, tlsSize); +#endif +} -emscripten_wasm_worker_t emscripten_create_wasm_worker(void *stackLowestAddress, uint32_t stackSize) +emscripten_wasm_worker_t emscripten_create_wasm_worker_no_tls(void *stackLowestAddress, uint32_t stackSize) { - uintptr_t stackBase = ((uintptr_t)stackLowestAddress + 15) & -16; - stackSize = ((uintptr_t)stackLowestAddress + stackSize - stackBase) & -16; - return _emscripten_create_wasm_worker((void*)stackBase, stackSize); +#if !WASM_WORKER_NO_TLS + return emscripten_create_wasm_worker(stackLowestAddress, stackSize, 0, 0); +#else + assert((uintptr_t)stackLowestAddress % 16 == 0); + assert(stackSize % 16 == 0); + assert(__builtin_wasm_tls_size() == 0 && "Cannot disable TLS with -sWASM_WORKERS_NO_TLS=1 when compiling code that does require TLS! Rebuild with -sWASM_WORKERS_NO_TLS=1 removed, or remove uses of TLS from the codebase."); + return _emscripten_create_wasm_worker_no_tls((void*)stackLowestAddress, stackSize); +#endif } void emscripten_wasm_worker_sleep(int64_t nsecs) diff --git a/tools/deps_info.py b/tools/deps_info.py index 2ebf9afdd850..166737729fa9 100644 --- a/tools/deps_info.py +++ b/tools/deps_info.py @@ -194,6 +194,8 @@ 'wgpuBufferGetMappedRange': ['malloc', 'free'], 'wgpuBufferGetConstMappedRange': ['malloc', 'free'], 'emscripten_glGetString': ['malloc'], + + '_wasm_worker_initializeRuntime': ['emscripten_wasm_worker_initialize'], # TODO: Once #15982 is resolved, this line is unnecessary. } diff --git a/tools/system_libs.py b/tools/system_libs.py index c38cde10f052..67a769cec063 100644 --- a/tools/system_libs.py +++ b/tools/system_libs.py @@ -1029,9 +1029,34 @@ def can_use(self): class libwasm_workers(MTLibrary): + def __init__(self, **kwargs): + self.tls = kwargs.pop('tls') + self.stack_check = kwargs.pop('stack_check') + self.debug = kwargs.pop('debug') + super().__init__(**kwargs) + name = 'libwasm_workers' - cflags = ['-Os', '-pthread'] + def get_cflags(self): + return ['-pthread', + '-D_DEBUG' if self.debug else '-Oz', + '-DSTACK_OVERFLOW_CHECK=' + ('2' if self.stack_check else '0'), + '-DWASM_WORKER_NO_TLS=' + ('0' if self.tls else '1')] + + def get_base_name(self): + name = 'libwasm_workers' + if not self.tls: name += '-notls' + if self.debug: name += '-debug' + if self.stack_check: name += '-stackcheck' + return name + + @classmethod + def vary_on(cls): + return super().vary_on() + ['tls', 'debug', 'stack_check'] + + @classmethod + def get_default_variation(cls, **kwargs): + return super().get_default_variation(tls=not settings.WASM_WORKERS_NO_TLS, debug=settings.ASSERTIONS>=1, stack_check=settings.STACK_OVERFLOW_CHECK==2, **kwargs) def get_files(self): return files_in_path( @@ -1105,7 +1130,7 @@ def get_ext(self): return '.o' def can_use(self): - return super().can_use() and settings.USE_PTHREADS + return super().can_use() and (settings.USE_PTHREADS or settings.WASM_WORKERS) class libcxxabi(NoExceptLibrary, MTLibrary): @@ -1741,7 +1766,7 @@ def add_library(libname): libs_to_link.append((lib.get_link_flag(), need_whole_archive)) if '-nostartfiles' not in args: - if settings.USE_PTHREADS: + if settings.USE_PTHREADS or settings.WASM_WORKERS: add_library('crtbegin') if settings.STANDALONE_WASM: From ead8aa5bf211e5a29c1502b3e142bcce297cbd42 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jukka=20Jyl=C3=A4nki?= Date: Tue, 15 Feb 2022 13:32:53 +0200 Subject: [PATCH 05/65] Add C++11 thread_local keyword test. --- tests/test_browser.py | 14 +++++--- tests/wasm_worker/cpp11_thread_local.cpp | 36 +++++++++++++++++++ ..._tls.S => wasm_worker_tls_wasm_assembly.S} | 0 ..._tls.c => wasm_worker_tls_wasm_assembly.c} | 0 4 files changed, 46 insertions(+), 4 deletions(-) create mode 100644 tests/wasm_worker/cpp11_thread_local.cpp rename tests/wasm_worker/{wasm_worker_tls.S => wasm_worker_tls_wasm_assembly.S} (100%) rename tests/wasm_worker/{wasm_worker_tls.c => wasm_worker_tls_wasm_assembly.c} (100%) diff --git a/tests/test_browser.py b/tests/test_browser.py index 15d7a710dab4..ec0bbc249b01 100644 --- a/tests/test_browser.py +++ b/tests/test_browser.py @@ -5166,11 +5166,17 @@ def test_wasm_worker_self_id(self): expected='0', args=['-s', 'WASM_WORKERS=1', '-s', 'MINIMAL_RUNTIME=1']) - # Tests TLS variables in Wasm Workers - def test_wasm_worker_tls(self): - self.btest(path_from_root('tests', 'wasm_worker', 'wasm_worker_tls.c'), + # Tests direct Wasm Assembly .S file based TLS variables in Wasm Workers + def test_wasm_worker_tls_wasm_assembly(self): + self.btest(path_from_root('tests', 'wasm_worker', 'wasm_worker_tls_wasm_assembly.c'), expected='42', - args=['-s', 'WASM_WORKERS=1', '-s', 'MINIMAL_RUNTIME=1', path_from_root('tests', 'wasm_worker', 'wasm_worker_tls.S')]) + args=['-s', 'WASM_WORKERS=1', '-s', 'MINIMAL_RUNTIME=1', path_from_root('tests', 'wasm_worker', 'wasm_worker_tls_wasm_assembly.S')]) + + # Tests C++11 keyword thread_local for TLS in Wasm Workers + def test_wasm_worker_cpp11_thread_local(self): + self.btest(path_from_root('tests', 'wasm_worker', 'cpp11_thread_local.cpp'), + expected='42', + args=['-s', 'WASM_WORKERS=1', '-s', 'MINIMAL_RUNTIME=1']) # Tests emscripten_wasm_worker_sleep() def test_wasm_worker_sleep(self): diff --git a/tests/wasm_worker/cpp11_thread_local.cpp b/tests/wasm_worker/cpp11_thread_local.cpp new file mode 100644 index 000000000000..0c3b705121a6 --- /dev/null +++ b/tests/wasm_worker/cpp11_thread_local.cpp @@ -0,0 +1,36 @@ +#include +#include +#include + +thread_local int tls = 1; + +void main_thread_func() +{ + assert(!emscripten_current_thread_is_wasm_worker()); + EM_ASM(console.log($0), tls); +#ifdef REPORT_RESULT + REPORT_RESULT(tls); +#endif +} + +void worker_main() +{ + assert(emscripten_current_thread_is_wasm_worker()); + assert(tls != 42); + assert(tls != 0); + assert(tls == 1); + tls = 123456; // Try to write garbage data to the memory location. + emscripten_wasm_worker_post_function_v(0, main_thread_func); +} + +char stack[1024]; +char tlsBase[1024]; + +int main() +{ + EM_ASM(console.log($0), tls); + assert(!emscripten_current_thread_is_wasm_worker()); + tls = 42; + emscripten_wasm_worker_t worker = emscripten_create_wasm_worker(stack, sizeof(stack), tlsBase, __builtin_wasm_tls_size()); + emscripten_wasm_worker_post_function_v(worker, worker_main); +} diff --git a/tests/wasm_worker/wasm_worker_tls.S b/tests/wasm_worker/wasm_worker_tls_wasm_assembly.S similarity index 100% rename from tests/wasm_worker/wasm_worker_tls.S rename to tests/wasm_worker/wasm_worker_tls_wasm_assembly.S diff --git a/tests/wasm_worker/wasm_worker_tls.c b/tests/wasm_worker/wasm_worker_tls_wasm_assembly.c similarity index 100% rename from tests/wasm_worker/wasm_worker_tls.c rename to tests/wasm_worker/wasm_worker_tls_wasm_assembly.c From 9545aebb6c681b90bc80179e0f2f4684814aebc7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jukka=20Jyl=C3=A4nki?= Date: Tue, 15 Feb 2022 13:36:42 +0200 Subject: [PATCH 06/65] Add test for C11 _Thread_local. --- tests/test_browser.py | 6 +++++ tests/wasm_worker/c11__Thread_local.c | 37 +++++++++++++++++++++++++++ 2 files changed, 43 insertions(+) create mode 100644 tests/wasm_worker/c11__Thread_local.c diff --git a/tests/test_browser.py b/tests/test_browser.py index ec0bbc249b01..2520aff8b197 100644 --- a/tests/test_browser.py +++ b/tests/test_browser.py @@ -5178,6 +5178,12 @@ def test_wasm_worker_cpp11_thread_local(self): expected='42', args=['-s', 'WASM_WORKERS=1', '-s', 'MINIMAL_RUNTIME=1']) + # Tests C11 keyword _Thread_local for TLS in Wasm Workers + def test_wasm_worker_c11__Thread_local(self): + self.btest(path_from_root('tests', 'wasm_worker', 'c11__Thread_local.c'), + expected='42', + args=['-s', 'WASM_WORKERS=1', '-s', 'MINIMAL_RUNTIME=1', '-std=gnu11']) # Cannot test C11 - because of EM_ASM must test Gnu11. + # Tests emscripten_wasm_worker_sleep() def test_wasm_worker_sleep(self): self.btest(path_from_root('tests', 'wasm_worker', 'wasm_worker_sleep.c'), diff --git a/tests/wasm_worker/c11__Thread_local.c b/tests/wasm_worker/c11__Thread_local.c new file mode 100644 index 000000000000..3d27ed489923 --- /dev/null +++ b/tests/wasm_worker/c11__Thread_local.c @@ -0,0 +1,37 @@ +#include +#include +#include +#include + +_Thread_local int tls = 1; + +void main_thread_func() +{ + assert(!emscripten_current_thread_is_wasm_worker()); + EM_ASM(console.log($0), tls); +#ifdef REPORT_RESULT + REPORT_RESULT(tls); +#endif +} + +void worker_main() +{ + assert(emscripten_current_thread_is_wasm_worker()); + assert(tls != 42); + assert(tls != 0); + assert(tls == 1); + tls = 123456; // Try to write garbage data to the memory location. + emscripten_wasm_worker_post_function_v(0, main_thread_func); +} + +char stack[1024]; +char tlsBase[1024]; + +int main() +{ + EM_ASM(console.log($0), tls); + assert(!emscripten_current_thread_is_wasm_worker()); + tls = 42; + emscripten_wasm_worker_t worker = emscripten_create_wasm_worker(stack, sizeof(stack), tlsBase, __builtin_wasm_tls_size()); + emscripten_wasm_worker_post_function_v(worker, worker_main); +} From 29bed037f91e7b7763f02ccad8d10e1d75a74ffa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jukka=20Jyl=C3=A4nki?= Date: Tue, 15 Feb 2022 14:13:54 +0200 Subject: [PATCH 07/65] Add emscripten_malloc_wasm_worker and rename creation API a little. --- src/library_wasm_worker.js | 10 +++---- system/include/emscripten/wasm_worker.h | 29 +++++++++++++++++-- system/lib/wasm_worker/library_wasm_worker.c | 24 +++++++++++---- tests/test_browser.py | 2 +- tests/wasm_worker/c11__Thread_local.c | 2 +- tests/wasm_worker/cpp11_thread_local.cpp | 2 +- .../hardware_concurrency_is_lock_free.c | 2 +- tests/wasm_worker/hello_wasm_worker.c | 6 ++-- tests/wasm_worker/lock_async_acquire.c | 2 +- .../wasm_worker/lock_busyspin_wait_acquire.c | 2 +- .../lock_busyspin_waitinf_acquire.c | 2 +- tests/wasm_worker/lock_wait_acquire.c | 2 +- tests/wasm_worker/lock_wait_acquire2.c | 4 +-- tests/wasm_worker/lock_waitinf_acquire.c | 2 +- tests/wasm_worker/no_proxied_js_functions.c | 2 +- tests/wasm_worker/post_function.c | 2 +- .../post_function_to_main_thread.c | 2 +- tests/wasm_worker/semaphore_waitinf_acquire.c | 4 +-- .../wasm_worker/terminate_all_wasm_workers.c | 4 +-- tests/wasm_worker/terminate_wasm_worker.c | 2 +- tests/wasm_worker/wait32_notify.c | 2 +- tests/wasm_worker/wait64_notify.c | 2 +- tests/wasm_worker/wait_async.c | 2 +- tests/wasm_worker/wasm_worker_self_id.c | 4 +-- tests/wasm_worker/wasm_worker_sleep.c | 2 +- .../wasm_worker_tls_wasm_assembly.c | 2 +- 26 files changed, 78 insertions(+), 43 deletions(-) diff --git a/src/library_wasm_worker.js b/src/library_wasm_worker.js index 365a14597e73..fb52ef3b7147 100644 --- a/src/library_wasm_worker.js +++ b/src/library_wasm_worker.js @@ -62,16 +62,16 @@ mergeInto(LibraryManager.library, { _wasmWorkerBlobUrl: "URL.createObjectURL(new Blob(['onmessage=function(d){onmessage=null;d=d.data;{{{ captureModuleArg() }}}importScripts(d.js);{{{ instantiateModule() }}}d.wasm=d.mem=d.js=0;}'],{type:'application/javascript'}))", #endif - _emscripten_create_wasm_worker__deps: ['wasm_workers', 'wasm_workers_id', '_wasm_worker_appendToQueue', '_wasm_worker_runPostMessage' + _emscripten_create_wasm_worker_with_tls__deps: ['wasm_workers', 'wasm_workers_id', '_wasm_worker_appendToQueue', '_wasm_worker_runPostMessage' #if WASM_WORKERS == 2 , '_wasmWorkerBlobUrl' #endif ], - _emscripten_create_wasm_worker__postset: 'if (ENVIRONMENT_IS_WASM_WORKER) {\n' + _emscripten_create_wasm_worker_with_tls__postset: 'if (ENVIRONMENT_IS_WASM_WORKER) {\n' + '_wasm_workers[0] = this;\n' + 'addEventListener("message", __wasm_worker_appendToQueue);\n' + '}\n', - _emscripten_create_wasm_worker: function(stackLowestAddress, stackSize, tlsAddress, tlsSize) { + _emscripten_create_wasm_worker_with_tls: function(stackLowestAddress, stackSize, tlsAddress, tlsSize) { #if ASSERTIONS assert(stackLowestAddress % 16 == 0); assert(stackSize % 16 == 0); @@ -102,9 +102,9 @@ mergeInto(LibraryManager.library, { worker.addEventListener('message', __wasm_worker_runPostMessage); return _wasm_workers_id++; }, - _emscripten_create_wasm_worker_no_tls__deps: ['_emscripten_create_wasm_worker'], + _emscripten_create_wasm_worker_no_tls__deps: ['_emscripten_create_wasm_worker_with_tls'], _emscripten_create_wasm_worker_no_tls: function(stackLowestAddress, stackSize) { - return __emscripten_create_wasm_worker(stackLowestAddress, stackSize, 0, 0); + return __emscripten_create_wasm_worker_with_tls(stackLowestAddress, stackSize, 0, 0); }, emscripten_terminate_wasm_worker: function(id) { diff --git a/system/include/emscripten/wasm_worker.h b/system/include/emscripten/wasm_worker.h index 769572525246..b2f957444255 100644 --- a/system/include/emscripten/wasm_worker.h +++ b/system/include/emscripten/wasm_worker.h @@ -10,8 +10,33 @@ extern "C" { #define emscripten_wasm_worker_t int #define EMSCRIPTEN_WASM_WORKER_ID_PARENT 0 -// If not building with Wasm workers enabled (-s WASM_WORKERS=0), returns 0. -emscripten_wasm_worker_t emscripten_create_wasm_worker(void *stackLowestAddress, uint32_t stackSize, void *tlsAddress, uint32_t tlsSize); +/* Creates a new Worker() that is attached to executing this WebAssembly.Instance and WebAssembly.Memory. + + emscripten_malloc_wasm_worker: Creates a new Worker, dynamically allocating stack and TLS for it. Unfortunately + due to the asynchronous no-notifications nature of how Worker API specification teardown + behaves, the dynamically allocated memory can never be freed, so use this function + only in scenarios where the page does not need to deinitialize/tear itself down. + + emscripten_create_wasm_worker_no_tls: Creates a Wasm Worker on the given placed stack address area, but no TLS. + Use this function on codebase that explicitly do not have any TLS state, + e.g. when the Worker is being reset to reinit execution at runtime, or to micro- + optimize code size when TLS is not needed. This function will assert() fail if + the compiled code does have any TLS uses in it. This function does not use any + dynamic memory allocation. + + emscripten_create_wasm_worker_with_tls: Creates a Wasm Worker on given placed stack address and TLS area. + Use the Wasm built-in functions __builtin_wasm_tls_align() and + __builtin_wasm_tls_size() to obtain the needed memory size for the TLS area. + Use this function to manually manage the memory that a Worker should use. + This function does not use any dynamic memory allocation. + + Returns an ID that represents the given Worker. If not building with Wasm workers enabled (-s WASM_WORKERS=0), + these functions will return 0 to denote failure. + Note that the Worker will be loaded up asynchronously, and initially will not be executing any code. Use + emscripten_wasm_worker_post_function_*() set of functions to start executing code on the Worker. */ +emscripten_wasm_worker_t emscripten_malloc_wasm_worker(uint32_t stackSize); +emscripten_wasm_worker_t emscripten_create_wasm_worker_no_tls(void *stackLowestAddress, uint32_t stackSize); +emscripten_wasm_worker_t emscripten_create_wasm_worker_with_tls(void *stackLowestAddress, uint32_t stackSize, void *tlsAddress, uint32_t tlsSize); // Exists, but is a no-op if not building with Wasm Workers enabled (-s WASM_WORKERS=0) void emscripten_terminate_wasm_worker(emscripten_wasm_worker_t id); diff --git a/system/lib/wasm_worker/library_wasm_worker.c b/system/lib/wasm_worker/library_wasm_worker.c index b99aecc72435..ca272a597634 100644 --- a/system/lib/wasm_worker/library_wasm_worker.c +++ b/system/lib/wasm_worker/library_wasm_worker.c @@ -2,6 +2,7 @@ #include #include #include +#include // Options: // #define WASM_WORKER_NO_TLS 0/1 : set to 1 to disable TLS compilation support for a small code size gain @@ -9,7 +10,7 @@ // Internal implementation function in JavaScript side that emscripten_create_wasm_worker() calls to // to perform the wasm worker creation. -emscripten_wasm_worker_t _emscripten_create_wasm_worker(void *stackLowestAddress, uint32_t stackSize, void *tlsAddress, uint32_t tlsSize); +emscripten_wasm_worker_t _emscripten_create_wasm_worker_with_tls(void *stackLowestAddress, uint32_t stackSize, void *tlsAddress, uint32_t tlsSize); emscripten_wasm_worker_t _emscripten_create_wasm_worker_no_tls(void *stackLowestAddress, uint32_t stackSize); void emscripten_stack_set_limits(uint32_t stackLowestAddress, uint32_t stackSize); @@ -57,24 +58,25 @@ void emscripten_wasm_worker_initialize(void *stackLowestAddress, uint32_t stackS #endif } -emscripten_wasm_worker_t emscripten_create_wasm_worker(void *stackLowestAddress, uint32_t stackSize, void *tlsAddress, uint32_t tlsSize) +emscripten_wasm_worker_t emscripten_create_wasm_worker_with_tls(void *stackLowestAddress, uint32_t stackSize, void *tlsAddress, uint32_t tlsSize) { #if WASM_WORKER_NO_TLS return emscripten_create_wasm_worker_no_tls(stackLowestAddress, stackSize); #else assert((uintptr_t)stackLowestAddress % 16 == 0); assert(stackSize % 16 == 0); - assert((uintptr_t)tlsAddress % __builtin_wasm_tls_align() == 0); - assert(tlsSize == __builtin_wasm_tls_size()); + assert((uintptr_t)tlsAddress % __builtin_wasm_tls_align() == 0 && "TLS memory address not aligned in a call to emscripten_create_wasm_worker_with_tls()! Please allocate memory with alignment from __builtin_wasm_tls_align() when creating a Wasm Worker!"); + assert(tlsSize != 0 || __builtin_wasm_tls_size() == 0 && "Program code contains TLS: please use function emscripten_create_wasm_worker_with_tls() to create a Wasm Worker!"); + assert(tlsSize == __builtin_wasm_tls_size() && "TLS size mismatch! Please reserve exactly __builtin_wasm_tls_size() TLS memory in a call to emscripten_create_wasm_worker_with_tls()"); assert(tlsAddress != 0 || tlsSize == 0); - return _emscripten_create_wasm_worker((void*)stackLowestAddress, stackSize, tlsAddress, tlsSize); + return _emscripten_create_wasm_worker_with_tls((void*)stackLowestAddress, stackSize, tlsAddress, tlsSize); #endif } emscripten_wasm_worker_t emscripten_create_wasm_worker_no_tls(void *stackLowestAddress, uint32_t stackSize) { #if !WASM_WORKER_NO_TLS - return emscripten_create_wasm_worker(stackLowestAddress, stackSize, 0, 0); + return emscripten_create_wasm_worker_with_tls(stackLowestAddress, stackSize, 0, 0); #else assert((uintptr_t)stackLowestAddress % 16 == 0); assert(stackSize % 16 == 0); @@ -83,6 +85,16 @@ emscripten_wasm_worker_t emscripten_create_wasm_worker_no_tls(void *stackLowestA #endif } +emscripten_wasm_worker_t emscripten_malloc_wasm_worker(uint32_t stackSize) +{ +#if WASM_WORKER_NO_TLS + return emscripten_create_wasm_worker_no_tls(memalign(16, stackSize), stackSize, 0, 0); +#else + uint32_t tlsSize = __builtin_wasm_tls_size(); + return emscripten_create_wasm_worker_with_tls(memalign(16, stackSize), stackSize, memalign(__builtin_wasm_tls_align(), tlsSize), tlsSize); +#endif +} + void emscripten_wasm_worker_sleep(int64_t nsecs) { int32_t addr = 0; diff --git a/tests/test_browser.py b/tests/test_browser.py index 2520aff8b197..638e24ca2a0a 100644 --- a/tests/test_browser.py +++ b/tests/test_browser.py @@ -5154,7 +5154,7 @@ def test_wasm2js_fallback_on_wasm_compilation_failure(self): def test_system(self): self.btest_exit(test_file('system.c')) - # Tests emscripten_create_wasm_worker() and emscripten_current_thread_is_wasm_worker() functions + # Tests emscripten_malloc_wasm_worker() and emscripten_current_thread_is_wasm_worker() functions def test_wasm_worker_hello(self): self.btest(path_from_root('tests', 'wasm_worker', 'hello_wasm_worker.c'), expected='0', diff --git a/tests/wasm_worker/c11__Thread_local.c b/tests/wasm_worker/c11__Thread_local.c index 3d27ed489923..05719d8d76e9 100644 --- a/tests/wasm_worker/c11__Thread_local.c +++ b/tests/wasm_worker/c11__Thread_local.c @@ -32,6 +32,6 @@ int main() EM_ASM(console.log($0), tls); assert(!emscripten_current_thread_is_wasm_worker()); tls = 42; - emscripten_wasm_worker_t worker = emscripten_create_wasm_worker(stack, sizeof(stack), tlsBase, __builtin_wasm_tls_size()); + emscripten_wasm_worker_t worker = emscripten_create_wasm_worker_with_tls(stack, sizeof(stack), tlsBase, __builtin_wasm_tls_size()); emscripten_wasm_worker_post_function_v(worker, worker_main); } diff --git a/tests/wasm_worker/cpp11_thread_local.cpp b/tests/wasm_worker/cpp11_thread_local.cpp index 0c3b705121a6..3dcc01bdd1c3 100644 --- a/tests/wasm_worker/cpp11_thread_local.cpp +++ b/tests/wasm_worker/cpp11_thread_local.cpp @@ -31,6 +31,6 @@ int main() EM_ASM(console.log($0), tls); assert(!emscripten_current_thread_is_wasm_worker()); tls = 42; - emscripten_wasm_worker_t worker = emscripten_create_wasm_worker(stack, sizeof(stack), tlsBase, __builtin_wasm_tls_size()); + emscripten_wasm_worker_t worker = emscripten_create_wasm_worker_with_tls(stack, sizeof(stack), tlsBase, __builtin_wasm_tls_size()); emscripten_wasm_worker_post_function_v(worker, worker_main); } diff --git a/tests/wasm_worker/hardware_concurrency_is_lock_free.c b/tests/wasm_worker/hardware_concurrency_is_lock_free.c index c7e47fcd61b4..39e26ac1650d 100644 --- a/tests/wasm_worker/hardware_concurrency_is_lock_free.c +++ b/tests/wasm_worker/hardware_concurrency_is_lock_free.c @@ -30,6 +30,6 @@ char stack[1024]; int main() { test(); - emscripten_wasm_worker_t worker = emscripten_create_wasm_worker(stack, sizeof(stack)); + emscripten_wasm_worker_t worker = emscripten_create_wasm_worker_no_tls(stack, sizeof(stack)); emscripten_wasm_worker_post_function_v(worker, worker_main); } diff --git a/tests/wasm_worker/hello_wasm_worker.c b/tests/wasm_worker/hello_wasm_worker.c index ac7f97eb8876..10f5a8fda033 100644 --- a/tests/wasm_worker/hello_wasm_worker.c +++ b/tests/wasm_worker/hello_wasm_worker.c @@ -2,7 +2,7 @@ #include #include -// Test emscripten_create_wasm_worker() and emscripten_current_thread_is_wasm_worker() functions +// Test emscripten_malloc_wasm_worker() and emscripten_current_thread_is_wasm_worker() functions EM_JS(void, console_log, (char* str), { console.log(UTF8ToString(str)); @@ -17,11 +17,9 @@ void worker_main() #endif } -char stack[1024]; - int main() { assert(!emscripten_current_thread_is_wasm_worker()); - emscripten_wasm_worker_t worker = emscripten_create_wasm_worker(stack, sizeof(stack)); + emscripten_wasm_worker_t worker = emscripten_malloc_wasm_worker(/*stack size: */1024); emscripten_wasm_worker_post_function_v(worker, worker_main); } diff --git a/tests/wasm_worker/lock_async_acquire.c b/tests/wasm_worker/lock_async_acquire.c index 4533a5ecc113..334ca0718618 100644 --- a/tests/wasm_worker/lock_async_acquire.c +++ b/tests/wasm_worker/lock_async_acquire.c @@ -100,7 +100,7 @@ int main() for(int i = 0; i < NUM_THREADS; ++i) { void *stack = malloc(1024); - emscripten_wasm_worker_t worker = emscripten_create_wasm_worker(stack, 1024); + emscripten_wasm_worker_t worker = emscripten_create_wasm_worker_no_tls(stack, 1024); emscripten_wasm_worker_post_function_vi(worker, (void (*)(int))schedule_work, 0); } diff --git a/tests/wasm_worker/lock_busyspin_wait_acquire.c b/tests/wasm_worker/lock_busyspin_wait_acquire.c index 292f5666b732..9f04a4573984 100644 --- a/tests/wasm_worker/lock_busyspin_wait_acquire.c +++ b/tests/wasm_worker/lock_busyspin_wait_acquire.c @@ -45,6 +45,6 @@ int main() { test(); - emscripten_wasm_worker_t worker = emscripten_create_wasm_worker(stack, sizeof(stack)); + emscripten_wasm_worker_t worker = emscripten_create_wasm_worker_no_tls(stack, sizeof(stack)); emscripten_wasm_worker_post_function_v(worker, worker_main); } diff --git a/tests/wasm_worker/lock_busyspin_waitinf_acquire.c b/tests/wasm_worker/lock_busyspin_waitinf_acquire.c index f58c2d6e93fa..a8459ab467f8 100644 --- a/tests/wasm_worker/lock_busyspin_waitinf_acquire.c +++ b/tests/wasm_worker/lock_busyspin_waitinf_acquire.c @@ -35,7 +35,7 @@ int main() // Spawn a Worker to try to take the lock. It will succeed only after releaseLock() // gets called. - emscripten_wasm_worker_t worker = emscripten_create_wasm_worker(stack, sizeof(stack)); + emscripten_wasm_worker_t worker = emscripten_create_wasm_worker_no_tls(stack, sizeof(stack)); emscripten_wasm_worker_post_function_v(worker, worker_main); emscripten_set_timeout(releaseLock, 1000, 0); diff --git a/tests/wasm_worker/lock_wait_acquire.c b/tests/wasm_worker/lock_wait_acquire.c index 82b8363ca607..6604a1220d23 100644 --- a/tests/wasm_worker/lock_wait_acquire.c +++ b/tests/wasm_worker/lock_wait_acquire.c @@ -42,6 +42,6 @@ char stack[1024]; int main() { - emscripten_wasm_worker_t worker = emscripten_create_wasm_worker(stack, sizeof(stack)); + emscripten_wasm_worker_t worker = emscripten_create_wasm_worker_no_tls(stack, sizeof(stack)); emscripten_wasm_worker_post_function_v(worker, worker_main); } diff --git a/tests/wasm_worker/lock_wait_acquire2.c b/tests/wasm_worker/lock_wait_acquire2.c index a4833a78f9b1..10f00d3d99a8 100644 --- a/tests/wasm_worker/lock_wait_acquire2.c +++ b/tests/wasm_worker/lock_wait_acquire2.c @@ -50,8 +50,8 @@ char stack2[1024]; int main() { - emscripten_wasm_worker_t worker1 = emscripten_create_wasm_worker(stack1, sizeof(stack1)); - emscripten_wasm_worker_t worker2 = emscripten_create_wasm_worker(stack2, sizeof(stack2)); + emscripten_wasm_worker_t worker1 = emscripten_create_wasm_worker_no_tls(stack1, sizeof(stack1)); + emscripten_wasm_worker_t worker2 = emscripten_create_wasm_worker_no_tls(stack2, sizeof(stack2)); emscripten_wasm_worker_post_function_v(worker1, worker1_main); emscripten_wasm_worker_post_function_v(worker2, worker2_main); } diff --git a/tests/wasm_worker/lock_waitinf_acquire.c b/tests/wasm_worker/lock_waitinf_acquire.c index cdc8b71ae738..e27d86197629 100644 --- a/tests/wasm_worker/lock_waitinf_acquire.c +++ b/tests/wasm_worker/lock_waitinf_acquire.c @@ -69,7 +69,7 @@ int main() for(int i = 0; i < NUM_THREADS; ++i) { void *stack = malloc(1024); - emscripten_wasm_worker_t worker = emscripten_create_wasm_worker(stack, 1024); + emscripten_wasm_worker_t worker = emscripten_create_wasm_worker_no_tls(stack, 1024); emscripten_wasm_worker_post_function_v(worker, worker_main); } } diff --git a/tests/wasm_worker/no_proxied_js_functions.c b/tests/wasm_worker/no_proxied_js_functions.c index c6282301b3da..ce6e0a9a4743 100644 --- a/tests/wasm_worker/no_proxied_js_functions.c +++ b/tests/wasm_worker/no_proxied_js_functions.c @@ -39,6 +39,6 @@ char stack[1024]; int main() { proxied_js_function(); - emscripten_wasm_worker_t worker = emscripten_create_wasm_worker(stack, sizeof(stack)); + emscripten_wasm_worker_t worker = emscripten_create_wasm_worker_no_tls(stack, sizeof(stack)); emscripten_wasm_worker_post_function_v(worker, worker_main); } diff --git a/tests/wasm_worker/post_function.c b/tests/wasm_worker/post_function.c index 288d3610d966..23899cb63118 100644 --- a/tests/wasm_worker/post_function.c +++ b/tests/wasm_worker/post_function.c @@ -94,7 +94,7 @@ char stack[1024]; int main() { assert(!emscripten_current_thread_is_wasm_worker()); - emscripten_wasm_worker_t worker = emscripten_create_wasm_worker(stack, sizeof(stack)); + emscripten_wasm_worker_t worker = emscripten_create_wasm_worker_no_tls(stack, sizeof(stack)); emscripten_wasm_worker_post_function_v(worker, v); emscripten_wasm_worker_post_function_vi(worker, vi, 1); emscripten_wasm_worker_post_function_vii(worker, vii, 2, 3); diff --git a/tests/wasm_worker/post_function_to_main_thread.c b/tests/wasm_worker/post_function_to_main_thread.c index 93aa2d3679c0..043a89cddb93 100644 --- a/tests/wasm_worker/post_function_to_main_thread.c +++ b/tests/wasm_worker/post_function_to_main_thread.c @@ -31,6 +31,6 @@ char stack[1024]; int main() { - emscripten_wasm_worker_t worker = emscripten_create_wasm_worker(stack, sizeof(stack)); + emscripten_wasm_worker_t worker = emscripten_create_wasm_worker_no_tls(stack, sizeof(stack)); emscripten_wasm_worker_post_function_v(worker, worker_main); } diff --git a/tests/wasm_worker/semaphore_waitinf_acquire.c b/tests/wasm_worker/semaphore_waitinf_acquire.c index cd4bd96aa786..9ebf2641dc42 100644 --- a/tests/wasm_worker/semaphore_waitinf_acquire.c +++ b/tests/wasm_worker/semaphore_waitinf_acquire.c @@ -78,14 +78,14 @@ int main() emscripten_semaphore_init(&threadsWaiting, 0); void *stack = malloc(1024); - emscripten_wasm_worker_t worker = emscripten_create_wasm_worker(stack, 1024); + emscripten_wasm_worker_t worker = emscripten_create_wasm_worker_no_tls(stack, 1024); emscripten_wasm_worker_post_function_v(worker, control_thread); #define NUM_THREADS 6 for(int i = 0; i < NUM_THREADS; ++i) { void *stack = malloc(1024); - emscripten_wasm_worker_t worker = emscripten_create_wasm_worker(stack, 1024); + emscripten_wasm_worker_t worker = emscripten_create_wasm_worker_no_tls(stack, 1024); emscripten_wasm_worker_post_function_v(worker, worker_main); } } diff --git a/tests/wasm_worker/terminate_all_wasm_workers.c b/tests/wasm_worker/terminate_all_wasm_workers.c index 385a5e31a1b4..018d0e38ddd4 100644 --- a/tests/wasm_worker/terminate_all_wasm_workers.c +++ b/tests/wasm_worker/terminate_all_wasm_workers.c @@ -75,8 +75,8 @@ void terminate_worker(void *userData) int main() { - worker[0] = emscripten_create_wasm_worker(stack1, sizeof(stack1)); - worker[1] = emscripten_create_wasm_worker(stack2, sizeof(stack2)); + worker[0] = emscripten_create_wasm_worker_no_tls(stack1, sizeof(stack1)); + worker[1] = emscripten_create_wasm_worker_no_tls(stack2, sizeof(stack2)); emscripten_wasm_worker_post_function_v(worker[0], worker_main); emscripten_wasm_worker_post_function_v(worker[1], worker_main); diff --git a/tests/wasm_worker/terminate_wasm_worker.c b/tests/wasm_worker/terminate_wasm_worker.c index ce3989bced61..45f86b3683ea 100644 --- a/tests/wasm_worker/terminate_wasm_worker.c +++ b/tests/wasm_worker/terminate_wasm_worker.c @@ -73,7 +73,7 @@ void terminate_worker(void *userData) int main() { - worker = emscripten_create_wasm_worker(stack, sizeof(stack)); + worker = emscripten_create_wasm_worker_no_tls(stack, sizeof(stack)); emscripten_wasm_worker_post_function_v(worker, worker_main); // Terminate the worker after a small delay diff --git a/tests/wasm_worker/wait32_notify.c b/tests/wasm_worker/wait32_notify.c index ab04be49bac5..25d84b2da24c 100644 --- a/tests/wasm_worker/wait32_notify.c +++ b/tests/wasm_worker/wait32_notify.c @@ -67,7 +67,7 @@ EM_BOOL main_loop(double time, void *userData) int main() { console_log("main: creating worker"); - emscripten_wasm_worker_t worker = emscripten_create_wasm_worker(stack, sizeof(stack)); + emscripten_wasm_worker_t worker = emscripten_create_wasm_worker_no_tls(stack, sizeof(stack)); console_log("main: posting function"); emscripten_wasm_worker_post_function_v(worker, worker_main); diff --git a/tests/wasm_worker/wait64_notify.c b/tests/wasm_worker/wait64_notify.c index 9c903978dc74..0f9a17a68334 100644 --- a/tests/wasm_worker/wait64_notify.c +++ b/tests/wasm_worker/wait64_notify.c @@ -67,7 +67,7 @@ EM_BOOL main_loop(double time, void *userData) int main() { console_log("main: creating worker"); - emscripten_wasm_worker_t worker = emscripten_create_wasm_worker(stack, sizeof(stack)); + emscripten_wasm_worker_t worker = emscripten_create_wasm_worker_no_tls(stack, sizeof(stack)); console_log("main: posting function"); emscripten_wasm_worker_post_function_v(worker, worker_main); diff --git a/tests/wasm_worker/wait_async.c b/tests/wasm_worker/wait_async.c index 9ad0fe1fc621..02632e35d2a8 100644 --- a/tests/wasm_worker/wait_async.c +++ b/tests/wasm_worker/wait_async.c @@ -59,7 +59,7 @@ void asyncWaitFinishedShouldBeOk(int32_t *ptr, uint32_t val, ATOMICS_WAIT_RESULT int main() { console_log("main: creating worker"); - emscripten_wasm_worker_t worker = emscripten_create_wasm_worker(stack, sizeof(stack)); + emscripten_wasm_worker_t worker = emscripten_create_wasm_worker_no_tls(stack, sizeof(stack)); console_log("main: posting function"); emscripten_wasm_worker_post_function_v(worker, worker_main); diff --git a/tests/wasm_worker/wasm_worker_self_id.c b/tests/wasm_worker/wasm_worker_self_id.c index ade1232242c5..e6491f5e5c8e 100644 --- a/tests/wasm_worker/wasm_worker_self_id.c +++ b/tests/wasm_worker/wasm_worker_self_id.c @@ -45,8 +45,8 @@ char stack2[1024]; int main() { assert(emscripten_wasm_worker_self_id() == 0); - worker1 = emscripten_create_wasm_worker(stack1, sizeof(stack1)); - worker2 = emscripten_create_wasm_worker(stack2, sizeof(stack2)); + worker1 = emscripten_create_wasm_worker_no_tls(stack1, sizeof(stack1)); + worker2 = emscripten_create_wasm_worker_no_tls(stack2, sizeof(stack2)); emscripten_wasm_worker_post_function_v(worker1, worker1_main); emscripten_wasm_worker_post_function_v(worker2, worker2_main); } diff --git a/tests/wasm_worker/wasm_worker_sleep.c b/tests/wasm_worker/wasm_worker_sleep.c index 4285389b2e99..d25ec8683889 100644 --- a/tests/wasm_worker/wasm_worker_sleep.c +++ b/tests/wasm_worker/wasm_worker_sleep.c @@ -19,6 +19,6 @@ char stack[1024]; int main() { - emscripten_wasm_worker_t worker = emscripten_create_wasm_worker(stack, sizeof(stack)); + emscripten_wasm_worker_t worker = emscripten_create_wasm_worker_no_tls(stack, sizeof(stack)); emscripten_wasm_worker_post_function_v(worker, worker_main); } diff --git a/tests/wasm_worker/wasm_worker_tls_wasm_assembly.c b/tests/wasm_worker/wasm_worker_tls_wasm_assembly.c index fe72228a8c59..81038c0e1199 100644 --- a/tests/wasm_worker/wasm_worker_tls_wasm_assembly.c +++ b/tests/wasm_worker/wasm_worker_tls_wasm_assembly.c @@ -35,6 +35,6 @@ int main() assert(globalData == 1); globalData = 2; set_tls_variable(42); - emscripten_wasm_worker_t worker = emscripten_create_wasm_worker(stack, sizeof(stack)); + emscripten_wasm_worker_t worker = emscripten_create_wasm_worker_no_tls(stack, sizeof(stack)); emscripten_wasm_worker_post_function_v(worker, worker_main); } From 0e3e3284b7606fcf65398dbccf029c8e99dcd93c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jukka=20Jyl=C3=A4nki?= Date: Tue, 15 Feb 2022 16:56:22 +0200 Subject: [PATCH 08/65] Add documentation for Wasm Workers. --- .../static/css/theme.css | 1 + site/source/docs/api_reference/index.rst | 4 + .../docs/api_reference/wasm_workers.rst | 221 ++++++++++++++++++ tests/test_browser.py | 8 +- tests/wasm_worker/hello_wasm_worker.c | 19 +- tests/wasm_worker/malloc_wasm_worker.c | 25 ++ 6 files changed, 264 insertions(+), 14 deletions(-) create mode 100644 site/source/docs/api_reference/wasm_workers.rst create mode 100644 tests/wasm_worker/malloc_wasm_worker.c diff --git a/site/source/_themes/emscripten_sphinx_rtd_theme/static/css/theme.css b/site/source/_themes/emscripten_sphinx_rtd_theme/static/css/theme.css index 07f99a3d7a82..a7b7eba2dc89 100644 --- a/site/source/_themes/emscripten_sphinx_rtd_theme/static/css/theme.css +++ b/site/source/_themes/emscripten_sphinx_rtd_theme/static/css/theme.css @@ -510,3 +510,4 @@ and (max-width : 480px) { } } +.cellborder { border: solid 1px black; } \ No newline at end of file diff --git a/site/source/docs/api_reference/index.rst b/site/source/docs/api_reference/index.rst index a60489f2ba82..4bd5b703e577 100644 --- a/site/source/docs/api_reference/index.rst +++ b/site/source/docs/api_reference/index.rst @@ -21,6 +21,9 @@ This section lists Emscripten's public API, organised by header file. At a very - :ref:`Fetch-API`: API for managing accesses to network XHR and IndexedDB. +- :ref:`wasm_workers`: + Enables writing multithreaded programs using a web-like API. + - :ref:`Module`: Global JavaScript object that can be used to control code execution and access exported methods. @@ -53,6 +56,7 @@ This section lists Emscripten's public API, organised by header file. At a very bind.h trace.h fiber.h + wasm_workers advanced-apis diff --git a/site/source/docs/api_reference/wasm_workers.rst b/site/source/docs/api_reference/wasm_workers.rst new file mode 100644 index 000000000000..324fb8e4c6a0 --- /dev/null +++ b/site/source/docs/api_reference/wasm_workers.rst @@ -0,0 +1,221 @@ +.. _wasm_workers: + +================ +Wasm Workers API +================ + +The Wasm Workers API enables C/C++ code to leverage Web Workers and shared +WebAssembly.Memory (SharedArrayBuffer) to build multithreaded programs +via a direct web-like programming API. + +Quick Example +============= + +.. code-block:: cpp + + #include + #include + + void run_in_worker() + { + printf("Hello from wasm worker!\n"); + } + + int main() + { + emscripten_wasm_worker_t worker = emscripten_malloc_wasm_worker(/*stack size: */1024); + emscripten_wasm_worker_post_function_v(worker, run_in_worker); + } + +Build the code by passing the Emscripten flag ``-sWASM_WORKERS=1`` at both compile +and link steps. The example code creates a new Worker on the main browser thread, +which shares the same WebAssembly.Module and WebAssembly.Memory object. Then a +``postMessage()`` is passed to the Worker to ask it to execute the function +``run_in_worker()`` to print a string. + +Introduction +============ + +In WebAssembly programs, the Memory object that contains the application state can be +shared across multiple Workers. This enables direct, high performance (and if explicit +care is not taken, racy!) access to synchronously share data state between multiple +Workers (shared state multithreading). + +Emscripten supports two multithreading APIs to leverage this web feature: + - POSIX Threads (Pthreads) API, and + - Wasm Workers API. + +The Pthreads API has a long history with native C programming and the POSIX standard, +while Wasm Workers API is unique to Emscripten compiler only. + +These two APIs provide largely the same feature set, but have important differences, +which this documentation seeks to explain to help decide which API one should target. + +Pthreads vs Wasm Workers: Which One to Use? +=========================================== + +The intended audience and use cases of these two multithreading APIs are slightly +different. + +The focus on Pthreads API is on portability and cross-platform compatibility. This API +is best used in scenarios where portability is most important, e.g. when a codebase is +cross-compiled to multiple platforms, like to a native Linux x64 executable and an +Emscripten WebAssembly based program. + +Pthreads API in Emscripten seeks to carefully emulate compatibility and the features that +the native Pthreads platforms already provide. This helps porting large C/C++ codebases +over to WebAssembly. + +Wasm Workers API on the other hand seeks to provide a more "direct mapping" to the web +multithreading primitives as they exist on the web. If an application is only developed to +target WebAssembly, and portability is not a concern, then using Wasm Workers can provide +great benefits in the form of simpler compiled output, less complexity, smaller code size +and better performance. + +However this benefit might not be an obvious win. The Pthreads API was designed to be useful +from the synchronous C/C++ language, whereas Web Workers are designed to be useful from +asynchronous JavaScript. WebAssembly C/C++ programs can find themselves somewhere in +the middle. + +To further understand the differences between Pthreads and Wasm Workers, refer to the following +table. + + +.. raw:: html + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FeaturePthreadsWasm Workers
Workers vs threadsWorker is either dormant or hosts an active thread.Each Worker is an active thread (colloquially synonymous).
Worker poolingDormant and active Workers reside in a system pool.
Worker returns to pool after its hosted thread terminates.
Not pooled (implement pooling yourself)
Thread startupThreads start synchronously and fast if enough dormant Workers available in pool to host, asynchronously and slow otherwise.Workers always start asynchronously and slow.
Thread entry pointThread starts with execution of an entry point function, returning from that function (by default) exits the thread.No concept of a thread entry point, created Workers are idle after creation, not executing any user code until functions are posted to them.
Thread terminationThread terminates by returning from entry point, or by calling
pthread_exit(code)
or by main thread calling
pthread_kill(code)
Worker cannot terminate itself, parent thread terminates by calling
emscripten_terminate_wasm_worker(worker)
Dynamic memory (malloc) utilizationRequires dynamic memory allocator, allocates memory for internal operation.Dynamic memory allocator not necessary, manual placement allocation possible.
Code size overheadFew hundred KBsFew KBs
Thread stack sizeSpecify in pthread_attr_t structure.Manage thread stack area explicitly with
emscripten_create_wasm_worker_*_tls()
functions, or +
automatically allocate stack with
emscripten_malloc_wasm_worker()
API.
Thread Local Storage (TLS)Supported transparently.Supported either explicitly with
emscripten_create_wasm_worker_*_tls()
functions, or +
automatically via
emscripten_malloc_wasm_worker()
API.
Thread IDCreating a pthread obtains its ID. Call
pthread_self()
to acquire ID of calling thread.
Creating a Worker obtains its ID. Call
emscripten_wasm_worker_self_id()
acquire ID of calling thread.
emscripten_get_now()All pthreads are synchronized to the same wallclock time base, so emscripten_get_now() return values across threads are comparable.Workers each have their own wallclock time base, emscripten_get_now() is not synchronized across them.
Synchronous blocking on main threadSynchronization primitives internally fall back to busy spin loops.Explicit spin vs sleep synchronization primitives.
Futex API
emscripten_futex_wait
emscripten_futex_wake
in emscripten/threading.h
emscripten_wasm_wait_i32
emscripten_wasm_wait_i64
emscripten_wasm_notify
in emscripten/wasm_workers.h
Asynchronous futex waitN/A
emscripten_atomic_wait_async()
emscripten_*_async_acquire()
However these are a difficult footgun, read WebAssembly/threads issue #176
C/C++ Function Proxyingemscripten/threading.h API for proxying function calls to other threads.Use emscripten_wasm_worker_post_function_*() API to message functions to other threads. These messages follow event queue semantics rather than proxy queue semantics.
JS Library Main Thread ProxyingUse the foo__proxy: 'sync'/'async' directive to specify a JS function to be run on the main thread context.N/A. JS code is always run on the calling thread context. Functions with __proxy directive will abort at runtime if called in a Worker.
Proxied EM_ASMUse MAIN_THREAD_EM_ASM() and MAIN_THREAD_ASYNC_EM_ASM() to proxy EM_ASM code blocks to the main thread.N/A. Only calling thread EM_ASM() function blocks are possible.
Build flagsCompile and link with -pthreadCompile and link with -sWASM_WORKERS=1
Preprocessor directives__EMSCRIPTEN_PTHREADS__=1 and __EMSCRIPTEN_SHARED_MEMORY__=1 are active__EMSCRIPTEN_PTHREADS__=1, __EMSCRIPTEN_SHARED_MEMORY__=1 and __EMSCRIPTEN_WASM_WORKERS__=1 are active
JS library directivesUSE_PTHREADS=1 and SHARED_MEMORY=1 are activeUSE_PTHREADS=1, SHARED_MEMORY=1 and WASM_WORKERS=1 are active
Atomics APISupported, use any of __atomic_* API, __sync_* API or C++11 std::atomic API.
Nonrecursive mutex
pthread_mutex_*
emscripten_lock_*
Recursive mutex
pthread_mutex_*
N/A
SemaphoresN/A
emscripten_semaphore_*
Condition Variables
pthread_cond_*
emscripten_condvar_*
Read-Write locks
pthread_rwlock_*
N/A
Spinlocks
pthread_spin_*
emscripten_lock_busyspin*
WebGL Offscreen Framebuffer
Supported with -sOFFSCREEN_FRAMEBUFFER=1
Not supported.
+ +Limitations and TODOs +===================== + +Currently it is not possible to simultaneously use pthreads and Wasm Workers in the same application, but this may change in the future. + +Also, the following build options are not supported at the moment with Wasm Workers: + +- -sSINGLE_FILE=1 +- Dynamic linking (-sLINKABLE=1, -sMAIN_MODULE=1, -sSIDE_MODULE=1) +- -sPROXY_TO_WORKER=1 +- -sPROXY_TO_PTHREAD=1 + +Example Code +============ + +See the directory tests/wasm_workers/ for code examples on different Wasm Workers API functionality. diff --git a/tests/test_browser.py b/tests/test_browser.py index 638e24ca2a0a..728d57f8c997 100644 --- a/tests/test_browser.py +++ b/tests/test_browser.py @@ -5154,12 +5154,18 @@ def test_wasm2js_fallback_on_wasm_compilation_failure(self): def test_system(self): self.btest_exit(test_file('system.c')) - # Tests emscripten_malloc_wasm_worker() and emscripten_current_thread_is_wasm_worker() functions + # Tests the hello_wasm_worker.c documentation example code. def test_wasm_worker_hello(self): self.btest(path_from_root('tests', 'wasm_worker', 'hello_wasm_worker.c'), expected='0', args=['-s', 'WASM_WORKERS=1', '-s', 'MINIMAL_RUNTIME=1']) + # Tests emscripten_malloc_wasm_worker() and emscripten_current_thread_is_wasm_worker() functions + def test_wasm_worker_malloc(self): + self.btest(path_from_root('tests', 'wasm_worker', 'wasm_worker_malloc.c'), + expected='0', + args=['-s', 'WASM_WORKERS=1', '-s', 'MINIMAL_RUNTIME=1']) + # Tests emscripten_wasm_worker_self_id() function def test_wasm_worker_self_id(self): self.btest(path_from_root('tests', 'wasm_worker', 'wasm_worker_self_id.c'), diff --git a/tests/wasm_worker/hello_wasm_worker.c b/tests/wasm_worker/hello_wasm_worker.c index 10f5a8fda033..eeeaba390a95 100644 --- a/tests/wasm_worker/hello_wasm_worker.c +++ b/tests/wasm_worker/hello_wasm_worker.c @@ -1,17 +1,11 @@ -#include #include -#include +#include -// Test emscripten_malloc_wasm_worker() and emscripten_current_thread_is_wasm_worker() functions +// This is the code example in site/source/docs/api_reference/wasm_workers.rst -EM_JS(void, console_log, (char* str), { - console.log(UTF8ToString(str)); -}); - -void worker_main() +void run_in_worker() { - console_log("Hello from wasm worker!"); - assert(emscripten_current_thread_is_wasm_worker()); + printf("Hello from wasm worker!\n"); #ifdef REPORT_RESULT REPORT_RESULT(0); #endif @@ -19,7 +13,6 @@ void worker_main() int main() { - assert(!emscripten_current_thread_is_wasm_worker()); - emscripten_wasm_worker_t worker = emscripten_malloc_wasm_worker(/*stack size: */1024); - emscripten_wasm_worker_post_function_v(worker, worker_main); + emscripten_wasm_worker_t worker = emscripten_malloc_wasm_worker(/*stack size: */1024); + emscripten_wasm_worker_post_function_v(worker, run_in_worker); } diff --git a/tests/wasm_worker/malloc_wasm_worker.c b/tests/wasm_worker/malloc_wasm_worker.c new file mode 100644 index 000000000000..10f5a8fda033 --- /dev/null +++ b/tests/wasm_worker/malloc_wasm_worker.c @@ -0,0 +1,25 @@ +#include +#include +#include + +// Test emscripten_malloc_wasm_worker() and emscripten_current_thread_is_wasm_worker() functions + +EM_JS(void, console_log, (char* str), { + console.log(UTF8ToString(str)); +}); + +void worker_main() +{ + console_log("Hello from wasm worker!"); + assert(emscripten_current_thread_is_wasm_worker()); +#ifdef REPORT_RESULT + REPORT_RESULT(0); +#endif +} + +int main() +{ + assert(!emscripten_current_thread_is_wasm_worker()); + emscripten_wasm_worker_t worker = emscripten_malloc_wasm_worker(/*stack size: */1024); + emscripten_wasm_worker_post_function_v(worker, worker_main); +} From 83fef04284b62f639817d050a858fad078c20423 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jukka=20Jyl=C3=A4nki?= Date: Thu, 17 Feb 2022 11:40:24 +0200 Subject: [PATCH 09/65] Flake and lint and fix build error --- .eslintrc.yml | 1 + src/jsifier.js | 4 ++-- system/lib/wasm_worker/library_wasm_worker.c | 2 +- tools/system_libs.py | 12 ++++++++---- 4 files changed, 12 insertions(+), 7 deletions(-) diff --git a/.eslintrc.yml b/.eslintrc.yml index 391ed268db98..e0d34de7d5d6 100644 --- a/.eslintrc.yml +++ b/.eslintrc.yml @@ -21,6 +21,7 @@ ignorePatterns: - "src/embind/" - "src/emrun_postjs.js" - "src/worker.js" + - "src/wasm_worker.js" - "src/wasm2js.js" - "src/webGLClient.js" - "src/webGLWorker.js" diff --git a/src/jsifier.js b/src/jsifier.js index a4490d52f19a..99a31b1bf858 100644 --- a/src/jsifier.js +++ b/src/jsifier.js @@ -297,8 +297,8 @@ function ${name}(${args}) { // (since there is no automatic proxying architecture available) contentText = modifyFunction(snippet, function(name, args, body) { return 'function ' + name + '(' + args + ') {\n' + - 'assert(!ENVIRONMENT_IS_WASM_WORKER, "Attempted to call proxied function \\"' + name + '\\" in a Wasm Worker, but in Wasm Worker enabled builds, proxied function architecture is not available!");\n' - + body + '}\n'; + 'assert(!ENVIRONMENT_IS_WASM_WORKER, "Attempted to call proxied function \\"' + name + '\\" in a Wasm Worker, but in Wasm Worker enabled builds, proxied function architecture is not available!");\n' + + body + '}\n'; }); } else if ((USE_ASAN || USE_LSAN || UBSAN_RUNTIME) && LibraryManager.library[ident + '__noleakcheck']) { contentText = modifyFunction(snippet, (name, args, body) => ` diff --git a/system/lib/wasm_worker/library_wasm_worker.c b/system/lib/wasm_worker/library_wasm_worker.c index ca272a597634..65b7802bdf85 100644 --- a/system/lib/wasm_worker/library_wasm_worker.c +++ b/system/lib/wasm_worker/library_wasm_worker.c @@ -88,7 +88,7 @@ emscripten_wasm_worker_t emscripten_create_wasm_worker_no_tls(void *stackLowestA emscripten_wasm_worker_t emscripten_malloc_wasm_worker(uint32_t stackSize) { #if WASM_WORKER_NO_TLS - return emscripten_create_wasm_worker_no_tls(memalign(16, stackSize), stackSize, 0, 0); + return emscripten_create_wasm_worker_no_tls(memalign(16, stackSize), stackSize); #else uint32_t tlsSize = __builtin_wasm_tls_size(); return emscripten_create_wasm_worker_with_tls(memalign(16, stackSize), stackSize, memalign(__builtin_wasm_tls_align(), tlsSize), tlsSize); diff --git a/tools/system_libs.py b/tools/system_libs.py index 67a769cec063..3867629b0bcf 100644 --- a/tools/system_libs.py +++ b/tools/system_libs.py @@ -503,6 +503,7 @@ def variations(cls): # pthreads and Wasm workers are currently not supported together. return [combo for combo in combos if not combo['is_mt'] or not combo['is_ww']] + class DebugLibrary(Library): def __init__(self, **kwargs): self.is_debug = kwargs.pop('is_debug') @@ -1045,9 +1046,12 @@ def get_cflags(self): def get_base_name(self): name = 'libwasm_workers' - if not self.tls: name += '-notls' - if self.debug: name += '-debug' - if self.stack_check: name += '-stackcheck' + if not self.tls: + name += '-notls' + if self.debug: + name += '-debug' + if self.stack_check: + name += '-stackcheck' return name @classmethod @@ -1056,7 +1060,7 @@ def vary_on(cls): @classmethod def get_default_variation(cls, **kwargs): - return super().get_default_variation(tls=not settings.WASM_WORKERS_NO_TLS, debug=settings.ASSERTIONS>=1, stack_check=settings.STACK_OVERFLOW_CHECK==2, **kwargs) + return super().get_default_variation(tls=not settings.WASM_WORKERS_NO_TLS, debug=settings.ASSERTIONS >= 1, stack_check=settings.STACK_OVERFLOW_CHECK == 2, **kwargs) def get_files(self): return files_in_path( From 57b2ff6d7ff63ae60d35ed9eab82404a77549aca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jukka=20Jyl=C3=A4nki?= Date: Thu, 17 Feb 2022 13:52:27 +0200 Subject: [PATCH 10/65] Remove deps_info dependency that does not work in current setup --- tools/deps_info.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/tools/deps_info.py b/tools/deps_info.py index 166737729fa9..2ebf9afdd850 100644 --- a/tools/deps_info.py +++ b/tools/deps_info.py @@ -194,8 +194,6 @@ 'wgpuBufferGetMappedRange': ['malloc', 'free'], 'wgpuBufferGetConstMappedRange': ['malloc', 'free'], 'emscripten_glGetString': ['malloc'], - - '_wasm_worker_initializeRuntime': ['emscripten_wasm_worker_initialize'], # TODO: Once #15982 is resolved, this line is unnecessary. } From 559551f80ebb4be022f5d4aea9059c9beefdefe5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jukka=20Jyl=C3=A4nki?= Date: Fri, 18 Feb 2022 10:21:53 +0200 Subject: [PATCH 11/65] __builtin_wasm_tls_align() can be zero --- system/lib/wasm_worker/library_wasm_worker.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/system/lib/wasm_worker/library_wasm_worker.c b/system/lib/wasm_worker/library_wasm_worker.c index 65b7802bdf85..b45ef9ac792f 100644 --- a/system/lib/wasm_worker/library_wasm_worker.c +++ b/system/lib/wasm_worker/library_wasm_worker.c @@ -42,7 +42,7 @@ void emscripten_wasm_worker_initialize(void *stackLowestAddress, uint32_t stackS assert(stackSize % 16 == 0); #if !WASM_WORKER_NO_TLS - assert((uintptr_t)tlsAddress % __builtin_wasm_tls_align() == 0); + assert(__builtin_wasm_tls_align() == 0 || (uintptr_t)tlsAddress % __builtin_wasm_tls_align() == 0); assert(tlsSize == __builtin_wasm_tls_size()); // Set up TLS __wasm_init_tls(tlsAddress); @@ -65,7 +65,7 @@ emscripten_wasm_worker_t emscripten_create_wasm_worker_with_tls(void *stackLowes #else assert((uintptr_t)stackLowestAddress % 16 == 0); assert(stackSize % 16 == 0); - assert((uintptr_t)tlsAddress % __builtin_wasm_tls_align() == 0 && "TLS memory address not aligned in a call to emscripten_create_wasm_worker_with_tls()! Please allocate memory with alignment from __builtin_wasm_tls_align() when creating a Wasm Worker!"); + assert(__builtin_wasm_tls_align() == 0 || (uintptr_t)tlsAddress % __builtin_wasm_tls_align() == 0 && "TLS memory address not aligned in a call to emscripten_create_wasm_worker_with_tls()! Please allocate memory with alignment from __builtin_wasm_tls_align() when creating a Wasm Worker!"); assert(tlsSize != 0 || __builtin_wasm_tls_size() == 0 && "Program code contains TLS: please use function emscripten_create_wasm_worker_with_tls() to create a Wasm Worker!"); assert(tlsSize == __builtin_wasm_tls_size() && "TLS size mismatch! Please reserve exactly __builtin_wasm_tls_size() TLS memory in a call to emscripten_create_wasm_worker_with_tls()"); assert(tlsAddress != 0 || tlsSize == 0); From 9effd63298817f386a804d43246dd1bd57f63735 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jukka=20Jyl=C3=A4nki?= Date: Fri, 18 Feb 2022 10:24:46 +0200 Subject: [PATCH 12/65] Add more notes about __builtin_wasm_tls_align() being zero --- system/include/emscripten/wasm_worker.h | 4 +++- system/lib/wasm_worker/library_wasm_worker.c | 1 + 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/system/include/emscripten/wasm_worker.h b/system/include/emscripten/wasm_worker.h index b2f957444255..b111824000e9 100644 --- a/system/include/emscripten/wasm_worker.h +++ b/system/include/emscripten/wasm_worker.h @@ -26,7 +26,9 @@ extern "C" { emscripten_create_wasm_worker_with_tls: Creates a Wasm Worker on given placed stack address and TLS area. Use the Wasm built-in functions __builtin_wasm_tls_align() and - __builtin_wasm_tls_size() to obtain the needed memory size for the TLS area. + __builtin_wasm_tls_size() to obtain the needed memory size for the TLS area + (though note that __builtin_wasm_tls_align() can return zero when the TLS size + is zero, so be careful to avoid a divide by zero if/when rounding to this alignment) Use this function to manually manage the memory that a Worker should use. This function does not use any dynamic memory allocation. diff --git a/system/lib/wasm_worker/library_wasm_worker.c b/system/lib/wasm_worker/library_wasm_worker.c index b45ef9ac792f..757bdb2a6e69 100644 --- a/system/lib/wasm_worker/library_wasm_worker.c +++ b/system/lib/wasm_worker/library_wasm_worker.c @@ -42,6 +42,7 @@ void emscripten_wasm_worker_initialize(void *stackLowestAddress, uint32_t stackS assert(stackSize % 16 == 0); #if !WASM_WORKER_NO_TLS + assert(__builtin_wasm_tls_align() != 0 || __builtin_wasm_tls_size() == 0); // Internal consistency check: Clang can report __builtin_wasm_tls_align to be zero. assert(__builtin_wasm_tls_align() == 0 || (uintptr_t)tlsAddress % __builtin_wasm_tls_align() == 0); assert(tlsSize == __builtin_wasm_tls_size()); // Set up TLS From 177f29b5202a678e28b8cb172874e7471ecb025d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jukka=20Jyl=C3=A4nki?= Date: Fri, 18 Feb 2022 11:14:58 +0200 Subject: [PATCH 13/65] Add test for GCC __thread keyword. --- tests/test_browser.py | 6 ++++++ tests/wasm_worker/gcc___Thread.c | 37 ++++++++++++++++++++++++++++++++ 2 files changed, 43 insertions(+) create mode 100644 tests/wasm_worker/gcc___Thread.c diff --git a/tests/test_browser.py b/tests/test_browser.py index 728d57f8c997..ba6ac72a78fe 100644 --- a/tests/test_browser.py +++ b/tests/test_browser.py @@ -5190,6 +5190,12 @@ def test_wasm_worker_c11__Thread_local(self): expected='42', args=['-s', 'WASM_WORKERS=1', '-s', 'MINIMAL_RUNTIME=1', '-std=gnu11']) # Cannot test C11 - because of EM_ASM must test Gnu11. + # Tests GCC specific extension keyword __thread for TLS in Wasm Workers + def test_wasm_worker_gcc___thread(self): + self.btest(path_from_root('tests', 'wasm_worker', 'gcc___thread.c'), + expected='42', + args=['-s', 'WASM_WORKERS=1', '-s', 'MINIMAL_RUNTIME=1', '-std=gnu11']) + # Tests emscripten_wasm_worker_sleep() def test_wasm_worker_sleep(self): self.btest(path_from_root('tests', 'wasm_worker', 'wasm_worker_sleep.c'), diff --git a/tests/wasm_worker/gcc___Thread.c b/tests/wasm_worker/gcc___Thread.c new file mode 100644 index 000000000000..e9808b63e76a --- /dev/null +++ b/tests/wasm_worker/gcc___Thread.c @@ -0,0 +1,37 @@ +#include +#include +#include +#include + +__thread int tls = 1; + +void main_thread_func() +{ + assert(!emscripten_current_thread_is_wasm_worker()); + EM_ASM(console.log($0), tls); +#ifdef REPORT_RESULT + REPORT_RESULT(tls); +#endif +} + +void worker_main() +{ + assert(emscripten_current_thread_is_wasm_worker()); + assert(tls != 42); + assert(tls != 0); + assert(tls == 1); + tls = 123456; // Try to write garbage data to the memory location. + emscripten_wasm_worker_post_function_v(0, main_thread_func); +} + +char stack[1024]; +char tlsBase[1024]; + +int main() +{ + EM_ASM(console.log($0), tls); + assert(!emscripten_current_thread_is_wasm_worker()); + tls = 42; + emscripten_wasm_worker_t worker = emscripten_create_wasm_worker_with_tls(stack, sizeof(stack), tlsBase, __builtin_wasm_tls_size()); + emscripten_wasm_worker_post_function_v(worker, worker_main); +} From 3e0cdb36676b02e7d28c97a737de6bea29de78f1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jukka=20Jyl=C3=A4nki?= Date: Fri, 18 Feb 2022 13:07:05 +0200 Subject: [PATCH 14/65] Fix test_wasm_worker_malloc --- tests/test_browser.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_browser.py b/tests/test_browser.py index ba6ac72a78fe..e2bb9e9f6e88 100644 --- a/tests/test_browser.py +++ b/tests/test_browser.py @@ -5162,7 +5162,7 @@ def test_wasm_worker_hello(self): # Tests emscripten_malloc_wasm_worker() and emscripten_current_thread_is_wasm_worker() functions def test_wasm_worker_malloc(self): - self.btest(path_from_root('tests', 'wasm_worker', 'wasm_worker_malloc.c'), + self.btest(path_from_root('tests', 'wasm_worker', 'malloc_wasm_worker.c'), expected='0', args=['-s', 'WASM_WORKERS=1', '-s', 'MINIMAL_RUNTIME=1']) From 883e539381cb517b6571637a414c925b7d39570d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jukka=20Jyl=C3=A4nki?= Date: Fri, 18 Feb 2022 15:00:50 +0200 Subject: [PATCH 15/65] Fix emscripten_lock_async_acquire() --- src/library_wasm_worker.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/library_wasm_worker.js b/src/library_wasm_worker.js index fb52ef3b7147..42e7d5414b7e 100644 --- a/src/library_wasm_worker.js +++ b/src/library_wasm_worker.js @@ -324,7 +324,7 @@ mergeInto(LibraryManager.library, { do { let val = Atomics.compareExchange(HEAPU32, lock >> 2, 0/*zero represents lock being free*/, 1/*one represents lock being acquired*/); if (!val) return dispatch(0, 0/*'ok'*/); - let wait = Atomics['waitAsync'](HEAPU32, lock >> 2, val, maxWaitMilliseconds); + var wait = Atomics['waitAsync'](HEAPU32, lock >> 2, val, maxWaitMilliseconds); } while(wait.value === 'not-equal'); #if ASSERTIONS assert(wait.async || wait.value === 'timed-out'); From a40313314814ccc6ac07e6b9b0e21f16ae896041 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jukka=20Jyl=C3=A4nki?= Date: Fri, 18 Feb 2022 15:01:55 +0200 Subject: [PATCH 16/65] Fix thread stack creation. --- system/lib/compiler-rt/stack_limits.S | 24 +++++++++++ system/lib/wasm_worker/library_wasm_worker.c | 36 +--------------- tests/test_browser.py | 10 +++++ tests/wasm_worker/lock_async_acquire.c | 2 +- tests/wasm_worker/lock_waitinf_acquire.c | 2 +- tests/wasm_worker/semaphore_waitinf_acquire.c | 4 +- tests/wasm_worker/thread_stack.c | 42 +++++++++++++++++++ 7 files changed, 81 insertions(+), 39 deletions(-) create mode 100644 tests/wasm_worker/thread_stack.c diff --git a/system/lib/compiler-rt/stack_limits.S b/system/lib/compiler-rt/stack_limits.S index 628345fc3df7..318f17a0973e 100644 --- a/system/lib/compiler-rt/stack_limits.S +++ b/system/lib/compiler-rt/stack_limits.S @@ -77,6 +77,30 @@ emscripten_stack_get_free: PTR.sub end_function +# TODO: Relocate the following to its own file wasm_worker.S, but need to figure out how to reference +# __stack_base and __stack_end globals from a separate file as externs in order for that to work. +.globl emscripten_wasm_worker_initialize +emscripten_wasm_worker_initialize: + .functype emscripten_wasm_worker_initialize (PTR, i32, PTR, i32) -> () + + local.get 0 + global.set __stack_end + + local.get 0 + local.get 1 + PTR.add + local.tee 0 + global.set __stack_base + + local.get 0 + global.set __stack_pointer + + local.get 2 + .functype __wasm_init_tls (PTR) -> () + call __wasm_init_tls + + end_function + # Add emscripten_stack_init to static ctors .section .init_array.1,"",@ .p2align ALIGN diff --git a/system/lib/wasm_worker/library_wasm_worker.c b/system/lib/wasm_worker/library_wasm_worker.c index 757bdb2a6e69..32a3ac93c51b 100644 --- a/system/lib/wasm_worker/library_wasm_worker.c +++ b/system/lib/wasm_worker/library_wasm_worker.c @@ -13,14 +13,7 @@ emscripten_wasm_worker_t _emscripten_create_wasm_worker_with_tls(void *stackLowestAddress, uint32_t stackSize, void *tlsAddress, uint32_t tlsSize); emscripten_wasm_worker_t _emscripten_create_wasm_worker_no_tls(void *stackLowestAddress, uint32_t stackSize); -void emscripten_stack_set_limits(uint32_t stackLowestAddress, uint32_t stackSize); void __wasm_init_tls(void *memory); -void stackRestore(uint32_t stackBase); - -#if STACK_OVERFLOW_CHECK == 2 -// TODO: -//void __set_stack_limits(uint32_t stackLowestAddress, uint32_t stackSize); -#endif #if !WASM_WORKER_NO_TLS __attribute__((constructor(48))) @@ -31,34 +24,6 @@ static void emscripten_wasm_worker_main_thread_initialize() { } #endif -// Called in the Wasm Worker thread context to set up the Worker stack space and TLS -void emscripten_wasm_worker_initialize(void *stackLowestAddress, uint32_t stackSize -#if !WASM_WORKER_NO_TLS - , void *tlsAddress, uint32_t tlsSize -#endif - ) { - - assert((uintptr_t)stackLowestAddress % 16 == 0); - assert(stackSize % 16 == 0); - -#if !WASM_WORKER_NO_TLS - assert(__builtin_wasm_tls_align() != 0 || __builtin_wasm_tls_size() == 0); // Internal consistency check: Clang can report __builtin_wasm_tls_align to be zero. - assert(__builtin_wasm_tls_align() == 0 || (uintptr_t)tlsAddress % __builtin_wasm_tls_align() == 0); - assert(tlsSize == __builtin_wasm_tls_size()); - // Set up TLS - __wasm_init_tls(tlsAddress); -#endif - - // Set up stack - uint32_t stackTop = (uint32_t)stackLowestAddress + stackSize; - emscripten_stack_set_limits(stackTop, stackSize); - stackRestore(stackTop); -#if STACK_OVERFLOW_CHECK == 2 - // TODO: Cannot call this function from C code, Binaryen does not want to emit the function then - //__set_stack_limits((uint32_t)stackLowestAddress, stackSize); -#endif -} - emscripten_wasm_worker_t emscripten_create_wasm_worker_with_tls(void *stackLowestAddress, uint32_t stackSize, void *tlsAddress, uint32_t tlsSize) { #if WASM_WORKER_NO_TLS @@ -66,6 +31,7 @@ emscripten_wasm_worker_t emscripten_create_wasm_worker_with_tls(void *stackLowes #else assert((uintptr_t)stackLowestAddress % 16 == 0); assert(stackSize % 16 == 0); + assert(__builtin_wasm_tls_align() != 0 || __builtin_wasm_tls_size() == 0); // Internal consistency check: Clang can report __builtin_wasm_tls_align to be zero if there is no TLS used - double check it never reports zero if it is used. assert(__builtin_wasm_tls_align() == 0 || (uintptr_t)tlsAddress % __builtin_wasm_tls_align() == 0 && "TLS memory address not aligned in a call to emscripten_create_wasm_worker_with_tls()! Please allocate memory with alignment from __builtin_wasm_tls_align() when creating a Wasm Worker!"); assert(tlsSize != 0 || __builtin_wasm_tls_size() == 0 && "Program code contains TLS: please use function emscripten_create_wasm_worker_with_tls() to create a Wasm Worker!"); assert(tlsSize == __builtin_wasm_tls_size() && "TLS size mismatch! Please reserve exactly __builtin_wasm_tls_size() TLS memory in a call to emscripten_create_wasm_worker_with_tls()"); diff --git a/tests/test_browser.py b/tests/test_browser.py index e2bb9e9f6e88..0c74f13a5ad2 100644 --- a/tests/test_browser.py +++ b/tests/test_browser.py @@ -5160,6 +5160,16 @@ def test_wasm_worker_hello(self): expected='0', args=['-s', 'WASM_WORKERS=1', '-s', 'MINIMAL_RUNTIME=1']) + # Tests Wasm Worker thread stack setup + @parameterized({ + '': ([],), + 'no_tls': (['-sWASM_WORKERS_NO_TLS=1'],), + }) + def test_wasm_worker_thread_stack(self, args): + self.btest(path_from_root('tests', 'wasm_worker', 'thread_stack.c'), + expected='0', + args=['-s', 'WASM_WORKERS=1', '-s', 'MINIMAL_RUNTIME=1'] + args) + # Tests emscripten_malloc_wasm_worker() and emscripten_current_thread_is_wasm_worker() functions def test_wasm_worker_malloc(self): self.btest(path_from_root('tests', 'wasm_worker', 'malloc_wasm_worker.c'), diff --git a/tests/wasm_worker/lock_async_acquire.c b/tests/wasm_worker/lock_async_acquire.c index 334ca0718618..1a8e876545bc 100644 --- a/tests/wasm_worker/lock_async_acquire.c +++ b/tests/wasm_worker/lock_async_acquire.c @@ -99,7 +99,7 @@ int main() #define NUM_THREADS 10 for(int i = 0; i < NUM_THREADS; ++i) { - void *stack = malloc(1024); + void *stack = memalign(16, 1024); emscripten_wasm_worker_t worker = emscripten_create_wasm_worker_no_tls(stack, 1024); emscripten_wasm_worker_post_function_vi(worker, (void (*)(int))schedule_work, 0); } diff --git a/tests/wasm_worker/lock_waitinf_acquire.c b/tests/wasm_worker/lock_waitinf_acquire.c index e27d86197629..c7afb93d0768 100644 --- a/tests/wasm_worker/lock_waitinf_acquire.c +++ b/tests/wasm_worker/lock_waitinf_acquire.c @@ -68,7 +68,7 @@ int main() numWorkersAlive = NUM_THREADS; for(int i = 0; i < NUM_THREADS; ++i) { - void *stack = malloc(1024); + void *stack = memalign(16, 1024); emscripten_wasm_worker_t worker = emscripten_create_wasm_worker_no_tls(stack, 1024); emscripten_wasm_worker_post_function_v(worker, worker_main); } diff --git a/tests/wasm_worker/semaphore_waitinf_acquire.c b/tests/wasm_worker/semaphore_waitinf_acquire.c index 9ebf2641dc42..893fd4af8ab3 100644 --- a/tests/wasm_worker/semaphore_waitinf_acquire.c +++ b/tests/wasm_worker/semaphore_waitinf_acquire.c @@ -77,14 +77,14 @@ int main() { emscripten_semaphore_init(&threadsWaiting, 0); - void *stack = malloc(1024); + void *stack = memalign(16, 1024); emscripten_wasm_worker_t worker = emscripten_create_wasm_worker_no_tls(stack, 1024); emscripten_wasm_worker_post_function_v(worker, control_thread); #define NUM_THREADS 6 for(int i = 0; i < NUM_THREADS; ++i) { - void *stack = malloc(1024); + void *stack = memalign(16, 1024); emscripten_wasm_worker_t worker = emscripten_create_wasm_worker_no_tls(stack, 1024); emscripten_wasm_worker_post_function_v(worker, worker_main); } diff --git a/tests/wasm_worker/thread_stack.c b/tests/wasm_worker/thread_stack.c new file mode 100644 index 000000000000..d50e16c5dc1c --- /dev/null +++ b/tests/wasm_worker/thread_stack.c @@ -0,0 +1,42 @@ +#include +#include +#include +#include +#include +#include + +#define THREAD_STACK_SIZE 1024 +#define NUM_THREADS 2 +void *thread_stack[NUM_THREADS]; + +volatile int threadsOk = 0; + +void test_stack(int i) +{ + EM_ASM(console.log(`In thread ${$0}, stack base=0x${$1.toString(16)}, end=0x${$2.toString(16)}`), i, emscripten_stack_get_base(), emscripten_stack_get_end()); + assert(emscripten_stack_get_base() == (uintptr_t)thread_stack[i] + THREAD_STACK_SIZE); + assert(emscripten_stack_get_end() == (uintptr_t)thread_stack[i]); + + int ok = __sync_fetch_and_add(&threadsOk, 1); + EM_ASM(console.log($0), ok); + if (ok == 1) + { + EM_ASM(console.log(`Test finished!`)); +#ifdef REPORT_RESULT + REPORT_RESULT(0); +#endif + } +} + +int main() +{ + EM_ASM(console.log(`Main thread stack base=0x${$0.toString(16)}, end=0x${$1.toString(16)}`), emscripten_stack_get_base(), emscripten_stack_get_end()); + + for(int i = 0; i < NUM_THREADS; ++i) + { + thread_stack[i] = memalign(16, THREAD_STACK_SIZE); + emscripten_wasm_worker_t worker = emscripten_create_wasm_worker_no_tls(thread_stack[i], THREAD_STACK_SIZE); + EM_ASM(console.log(`Created thread ${$0} with stack ptr=0x${$1.toString(16)}, size=0x${$2.toString(16)}`), i, thread_stack[i], THREAD_STACK_SIZE); + emscripten_wasm_worker_post_function_vi(worker, test_stack, i); + } +} From 67d0bbb9df67398ca582a1800cec68b6c56e616b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jukka=20Jyl=C3=A4nki?= Date: Fri, 18 Feb 2022 18:47:02 +0200 Subject: [PATCH 17/65] Fix wasm64 build --- system/lib/compiler-rt/stack_limits.S | 3 +++ 1 file changed, 3 insertions(+) diff --git a/system/lib/compiler-rt/stack_limits.S b/system/lib/compiler-rt/stack_limits.S index 318f17a0973e..a137b62d2d61 100644 --- a/system/lib/compiler-rt/stack_limits.S +++ b/system/lib/compiler-rt/stack_limits.S @@ -88,6 +88,9 @@ emscripten_wasm_worker_initialize: local.get 0 local.get 1 +#ifdef __wasm64__ + i64.extend_i32_u +#endif PTR.add local.tee 0 global.set __stack_base From ebc4319e3525348e6742cd9f7a8ba67c743b161a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jukka=20Jyl=C3=A4nki?= Date: Fri, 18 Feb 2022 18:51:44 +0200 Subject: [PATCH 18/65] Add slack to lock_busyspin_wait_acquire --- tests/wasm_worker/lock_busyspin_wait_acquire.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/wasm_worker/lock_busyspin_wait_acquire.c b/tests/wasm_worker/lock_busyspin_wait_acquire.c index 9f04a4573984..6a250a50f67e 100644 --- a/tests/wasm_worker/lock_busyspin_wait_acquire.c +++ b/tests/wasm_worker/lock_busyspin_wait_acquire.c @@ -26,7 +26,7 @@ void test() success = emscripten_lock_busyspin_wait_acquire(&lock, 1000.0); // We already have the lock, and emscripten_lock is not recursive, so this should fail. t1 = emscripten_performance_now(); assert(!success); - assert(t1 - t0 >= 1000); // We should have waited for the requested duration for the lock. + assert(t1 - t0 >= 900); // We should have waited for the requested duration for the lock.. apply some slack since timing can have some noise. emscripten_lock_release(&lock); } From da84c92332abbb00c3078562871de2d05e588fa6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jukka=20Jyl=C3=A4nki?= Date: Fri, 18 Feb 2022 19:42:29 +0200 Subject: [PATCH 19/65] Fix typo in setting --- emcc.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/emcc.py b/emcc.py index 9e4fb85e6e1d..bff43768410c 100755 --- a/emcc.py +++ b/emcc.py @@ -2307,7 +2307,7 @@ def check_memory_setting(setting): # a .mem file in most cases, since it is binary & compact). however, for # shared memory builds we must keep the memory segments in the wasm as # they will be passive segments which the .mem format cannot handle. - settings.MEM_INIT_IN_WASM = not options.memory_init_file or settings.SINGLE_FILE or settings.USE_SHARED_MEMORY + settings.MEM_INIT_IN_WASM = not options.memory_init_file or settings.SINGLE_FILE or settings.SHARED_MEMORY else: # wasm includes the mem init in the wasm binary. The exception is # wasm2js, which behaves more like js. From ffdc68e90ec0f631bbafafcaf9ee634588e1ff7f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jukka=20Jyl=C3=A4nki?= Date: Sun, 20 Feb 2022 10:44:52 +0200 Subject: [PATCH 20/65] Remove removal of TextDecoder in threads. --- emcc.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/emcc.py b/emcc.py index bff43768410c..d722bb497bf1 100755 --- a/emcc.py +++ b/emcc.py @@ -2038,10 +2038,6 @@ def phase_linker_setup(options, state, newargs, user_settings): # overrides that. default_setting(user_settings, 'ABORTING_MALLOC', 0) - if settings.SHARED_MEMORY: - # UTF8Decoder.decode doesn't work with a view of a SharedArrayBuffer - settings.TEXTDECODER = 0 - if settings.USE_PTHREADS: if settings.USE_PTHREADS == 2: exit_with_error('USE_PTHREADS=2 is no longer supported') From 2a0f5e4add67ee9dfdbf093eb83088ddad044b7f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jukka=20Jyl=C3=A4nki?= Date: Sun, 20 Feb 2022 11:19:31 +0200 Subject: [PATCH 21/65] Fix non-Wasm Workers build --- emcc.py | 2 +- system/lib/compiler-rt/stack_limits.S | 2 ++ tools/system_libs.py | 8 +++++--- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/emcc.py b/emcc.py index d722bb497bf1..448dab08a8dc 100755 --- a/emcc.py +++ b/emcc.py @@ -1768,7 +1768,7 @@ def phase_linker_setup(options, state, newargs, user_settings): '$mergeLibSymbols', ] - if settings.USE_PTHREADS: # or settings.WASM_WORKERS: + if settings.USE_PTHREADS: settings.DEFAULT_LIBRARY_FUNCS_TO_INCLUDE += [ '$registerTlsInit', ] diff --git a/system/lib/compiler-rt/stack_limits.S b/system/lib/compiler-rt/stack_limits.S index a137b62d2d61..28d28ec01b58 100644 --- a/system/lib/compiler-rt/stack_limits.S +++ b/system/lib/compiler-rt/stack_limits.S @@ -77,6 +77,7 @@ emscripten_stack_get_free: PTR.sub end_function +#ifdef __EMSCRIPTEN_WASM_WORKERS__ # TODO: Relocate the following to its own file wasm_worker.S, but need to figure out how to reference # __stack_base and __stack_end globals from a separate file as externs in order for that to work. .globl emscripten_wasm_worker_initialize @@ -103,6 +104,7 @@ emscripten_wasm_worker_initialize: call __wasm_init_tls end_function +#endif # Add emscripten_stack_init to static ctors .section .init_array.1,"",@ diff --git a/tools/system_libs.py b/tools/system_libs.py index 3867629b0bcf..6282740f7b56 100644 --- a/tools/system_libs.py +++ b/tools/system_libs.py @@ -50,6 +50,8 @@ def get_base_cflags(force_object_files=False): flags += ['-sRELOCATABLE'] if settings.MEMORY64: flags += ['-sMEMORY64=' + str(settings.MEMORY64)] + if settings.WASM_WORKERS: + flags += ['-sWASM_WORKERS'] return flags @@ -478,7 +480,7 @@ def get_cflags(self): if self.is_mt: cflags += ['-sUSE_PTHREADS'] if self.is_ww: - cflags += ['-s', 'WASM_WORKERS=1'] + cflags += ['-sWASM_WORKERS'] return cflags def get_base_name(self): @@ -1149,7 +1151,7 @@ class libcxxabi(NoExceptLibrary, MTLibrary): def get_cflags(self): cflags = super().get_cflags() cflags.append('-DNDEBUG') - if not self.is_mt: + if not self.is_mt and not self.is_ww: cflags.append('-D_LIBCXXABI_HAS_NO_THREADS') if self.eh_mode == Exceptions.NONE: cflags.append('-D_LIBCXXABI_NO_EXCEPTIONS') @@ -1234,7 +1236,7 @@ def can_use(self): def get_cflags(self): cflags = super().get_cflags() cflags.append('-DNDEBUG') - if not self.is_mt: + if not self.is_mt and not self.is_ww: cflags.append('-D_LIBUNWIND_HAS_NO_THREADS') if self.eh_mode == Exceptions.NONE: cflags.append('-D_LIBUNWIND_HAS_NO_EXCEPTIONS') From 19904b4cab2e7b47db5429b565bd513f53236ff7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jukka=20Jyl=C3=A4nki?= Date: Sun, 20 Feb 2022 15:40:17 +0200 Subject: [PATCH 22/65] Fix file system case sensitivity --- tests/test_browser.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_browser.py b/tests/test_browser.py index 0c74f13a5ad2..327ee70d0832 100644 --- a/tests/test_browser.py +++ b/tests/test_browser.py @@ -5202,7 +5202,7 @@ def test_wasm_worker_c11__Thread_local(self): # Tests GCC specific extension keyword __thread for TLS in Wasm Workers def test_wasm_worker_gcc___thread(self): - self.btest(path_from_root('tests', 'wasm_worker', 'gcc___thread.c'), + self.btest(path_from_root('tests', 'wasm_worker', 'gcc___Thread.c'), expected='42', args=['-s', 'WASM_WORKERS=1', '-s', 'MINIMAL_RUNTIME=1', '-std=gnu11']) From 0300d021d2c2ebf86713c1df18334d95e9633968 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jukka=20Jyl=C3=A4nki?= Date: Sun, 20 Feb 2022 18:12:37 +0200 Subject: [PATCH 23/65] Fix Wasm Workers proxying mode generation. --- src/jsifier.js | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/src/jsifier.js b/src/jsifier.js index 99a31b1bf858..c694c7ad4d44 100644 --- a/src/jsifier.js +++ b/src/jsifier.js @@ -279,27 +279,29 @@ function ${name}(${args}) { if (isFunction) { // Emit the body of a JS library function. const proxyingMode = LibraryManager.library[ident + '__proxy']; - if (USE_PTHREADS && proxyingMode) { + if (SHARED_MEMORY && proxyingMode) { if (proxyingMode !== 'sync' && proxyingMode !== 'async') { throw new Error(`Invalid proxyingMode ${ident}__proxy: '${proxyingMode}' specified!`); } const sync = proxyingMode === 'sync'; assert(typeof original == 'function'); - contentText = modifyFunction(snippet, (name, args, body) => ` + if (USE_PTHREADS) { + contentText = modifyFunction(snippet, (name, args, body) => ` function ${name}(${args}) { if (ENVIRONMENT_IS_PTHREAD) return _emscripten_proxy_to_main_thread_js(${proxiedFunctionTable.length}, ${+sync}${args ? ', ' : ''}${args}); ${body} }\n`); + } else if (WASM_WORKERS && ASSERTIONS) { + // In ASSERTIONS builds add runtime checks that proxied functions are not attempted to be called in Wasm Workers + // (since there is no automatic proxying architecture available) + contentText = modifyFunction(snippet, (name, args, body) => ` +function ${name}(${args}) { + assert(!ENVIRONMENT_IS_WASM_WORKER, "Attempted to call proxied function "${name}" in a Wasm Worker, but in Wasm Worker enabled builds, proxied function architecture is not available!"); + ${body} +}\n`); + } proxiedFunctionTable.push(finalName); - } else if (WASM_WORKERS && ASSERTIONS && proxyingMode) { - // In ASSERTIONS builds add runtime checks that proxied functions are not attempted to be called in Wasm Workers - // (since there is no automatic proxying architecture available) - contentText = modifyFunction(snippet, function(name, args, body) { - return 'function ' + name + '(' + args + ') {\n' + - 'assert(!ENVIRONMENT_IS_WASM_WORKER, "Attempted to call proxied function \\"' + name + '\\" in a Wasm Worker, but in Wasm Worker enabled builds, proxied function architecture is not available!");\n' + - body + '}\n'; - }); } else if ((USE_ASAN || USE_LSAN || UBSAN_RUNTIME) && LibraryManager.library[ident + '__noleakcheck']) { contentText = modifyFunction(snippet, (name, args, body) => ` function ${name}(${args}) { From 9f87d160a199edf8a9f59de57fb36f7eacd2b155 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jukka=20Jyl=C3=A4nki?= Date: Sun, 20 Feb 2022 18:13:02 +0200 Subject: [PATCH 24/65] Skip TLS tests on Linux, they produce an internal compiler error. --- tests/test_browser.py | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/tests/test_browser.py b/tests/test_browser.py index 327ee70d0832..bfde2fb310fa 100644 --- a/tests/test_browser.py +++ b/tests/test_browser.py @@ -5190,18 +5190,49 @@ def test_wasm_worker_tls_wasm_assembly(self): # Tests C++11 keyword thread_local for TLS in Wasm Workers def test_wasm_worker_cpp11_thread_local(self): + self.skipTest('''wasm-ld: /b/s/w/ir/cache/builder/emscripten-releases/llvm-project/llvm/include/llvm/ADT/Optional.h:199: const T &llvm::optional_detail::OptionalStorage::getValue() const & [T = unsigned int]: Assertion `hasVal' failed. +PLEASE submit a bug report to https://github.com/llvm/llvm-project/issues/ and include the crash backtrace. +Stack dump: + #8 0x000000000068173f lld::wasm::GlobalSymbol::getGlobalIndex() const (/root/emsdk/upstream/bin/wasm-ld+0x68173f) + #9 0x0000000000699894 lld::wasm::GlobalSection::generateRelocationCode(llvm::raw_ostream&, bool) const (/root/emsdk/upstream/bin/wasm-ld+0x699894) +#10 0x0000000000688888 lld::wasm::(anonymous namespace)::Writer::run() Writer.cpp:0:0 +#11 0x0000000000682524 lld::wasm::writeResult() (/root/emsdk/upstream/bin/wasm-ld+0x682524) +#12 0x0000000000662f89 lld::wasm::(anonymous namespace)::LinkerDriver::linkerMain(llvm::ArrayRef) Driver.cpp:0:0 +#13 0x000000000065dd5e lld::wasm::link(llvm::ArrayRef, llvm::raw_ostream&, llvm::raw_ostream&, bool, bool) (/root/emsdk/upstream/bin/wasm-ld+0x65dd5e) +#14 0x000000000036f799 lldMain(int, char const**, llvm::raw_ostream&, llvm::raw_ostream&, bool) lld.cpp:0:0''') self.btest(path_from_root('tests', 'wasm_worker', 'cpp11_thread_local.cpp'), expected='42', args=['-s', 'WASM_WORKERS=1', '-s', 'MINIMAL_RUNTIME=1']) # Tests C11 keyword _Thread_local for TLS in Wasm Workers def test_wasm_worker_c11__Thread_local(self): + self.skipTest('''wasm-ld: /b/s/w/ir/cache/builder/emscripten-releases/llvm-project/llvm/include/llvm/ADT/Optional.h:199: const T &llvm::optional_detail::OptionalStorage::getValue() const & [T = unsigned int]: Assertion `hasVal' failed. +PLEASE submit a bug report to https://github.com/llvm/llvm-project/issues/ and include the crash backtrace. +Stack dump: + #8 0x000000000068173f lld::wasm::GlobalSymbol::getGlobalIndex() const (/root/emsdk/upstream/bin/wasm-ld+0x68173f) + #9 0x0000000000699894 lld::wasm::GlobalSection::generateRelocationCode(llvm::raw_ostream&, bool) const (/root/emsdk/upstream/bin/wasm-ld+0x699894) +#10 0x0000000000688888 lld::wasm::(anonymous namespace)::Writer::run() Writer.cpp:0:0 +#11 0x0000000000682524 lld::wasm::writeResult() (/root/emsdk/upstream/bin/wasm-ld+0x682524) +#12 0x0000000000662f89 lld::wasm::(anonymous namespace)::LinkerDriver::linkerMain(llvm::ArrayRef) Driver.cpp:0:0 +#13 0x000000000065dd5e lld::wasm::link(llvm::ArrayRef, llvm::raw_ostream&, llvm::raw_ostream&, bool, bool) (/root/emsdk/upstream/bin/wasm-ld+0x65dd5e) +#14 0x000000000036f799 lldMain(int, char const**, llvm::raw_ostream&, llvm::raw_ostream&, bool) lld.cpp:0:0''') + self.btest(path_from_root('tests', 'wasm_worker', 'c11__Thread_local.c'), expected='42', args=['-s', 'WASM_WORKERS=1', '-s', 'MINIMAL_RUNTIME=1', '-std=gnu11']) # Cannot test C11 - because of EM_ASM must test Gnu11. # Tests GCC specific extension keyword __thread for TLS in Wasm Workers def test_wasm_worker_gcc___thread(self): + self.skipTest('''wasm-ld: /b/s/w/ir/cache/builder/emscripten-releases/llvm-project/llvm/include/llvm/ADT/Optional.h:199: const T &llvm::optional_detail::OptionalStorage::getValue() const & [T = unsigned int]: Assertion `hasVal' failed. +PLEASE submit a bug report to https://github.com/llvm/llvm-project/issues/ and include the crash backtrace. +Stack dump: + #8 0x000000000068173f lld::wasm::GlobalSymbol::getGlobalIndex() const (/root/emsdk/upstream/bin/wasm-ld+0x68173f) + #9 0x0000000000699894 lld::wasm::GlobalSection::generateRelocationCode(llvm::raw_ostream&, bool) const (/root/emsdk/upstream/bin/wasm-ld+0x699894) +#10 0x0000000000688888 lld::wasm::(anonymous namespace)::Writer::run() Writer.cpp:0:0 +#11 0x0000000000682524 lld::wasm::writeResult() (/root/emsdk/upstream/bin/wasm-ld+0x682524) +#12 0x0000000000662f89 lld::wasm::(anonymous namespace)::LinkerDriver::linkerMain(llvm::ArrayRef) Driver.cpp:0:0 +#13 0x000000000065dd5e lld::wasm::link(llvm::ArrayRef, llvm::raw_ostream&, llvm::raw_ostream&, bool, bool) (/root/emsdk/upstream/bin/wasm-ld+0x65dd5e) +#14 0x000000000036f799 lldMain(int, char const**, llvm::raw_ostream&, llvm::raw_ostream&, bool) lld.cpp:0:0''') self.btest(path_from_root('tests', 'wasm_worker', 'gcc___Thread.c'), expected='42', args=['-s', 'WASM_WORKERS=1', '-s', 'MINIMAL_RUNTIME=1', '-std=gnu11']) From df81c829658af2b4b516216649efa1c9d1300139 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jukka=20Jyl=C3=A4nki?= Date: Sun, 20 Feb 2022 18:14:04 +0200 Subject: [PATCH 25/65] Fix typo --- src/jsifier.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/jsifier.js b/src/jsifier.js index c694c7ad4d44..ebcdc200529d 100644 --- a/src/jsifier.js +++ b/src/jsifier.js @@ -297,7 +297,7 @@ function ${name}(${args}) { // (since there is no automatic proxying architecture available) contentText = modifyFunction(snippet, (name, args, body) => ` function ${name}(${args}) { - assert(!ENVIRONMENT_IS_WASM_WORKER, "Attempted to call proxied function "${name}" in a Wasm Worker, but in Wasm Worker enabled builds, proxied function architecture is not available!"); + assert(!ENVIRONMENT_IS_WASM_WORKER, "Attempted to call proxied function '${name}' in a Wasm Worker, but in Wasm Worker enabled builds, proxied function architecture is not available!"); ${body} }\n`); } From fd5ed598e0a00cc2c9b918d4868b9839e442248a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jukka=20Jyl=C3=A4nki?= Date: Sun, 20 Feb 2022 19:17:21 +0200 Subject: [PATCH 26/65] Fix wasm_worker.h include from C code. --- system/include/emscripten/wasm_worker.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/system/include/emscripten/wasm_worker.h b/system/include/emscripten/wasm_worker.h index b111824000e9..a06e03630626 100644 --- a/system/include/emscripten/wasm_worker.h +++ b/system/include/emscripten/wasm_worker.h @@ -89,7 +89,7 @@ void emscripten_wasm_worker_post_function_sig(emscripten_wasm_worker_t id, void // If the given memory address contains value 'expectedValue', puts the calling thread to sleep to wait for that address to be notified. // Returns one of the ATOMICS_WAIT_* return codes. // NOTE: This function takes in the wait value in int64_t nanosecond units. Pass in maxWaitNanoseconds = -1 (or ATOMICS_WAIT_DURATION_INFINITE) to wait infinitely long. -static inline __attribute__((always_inline)) ATOMICS_WAIT_RESULT_T emscripten_wasm_wait_i32(int32_t *address, int expectedValue, int64_t maxWaitNanoseconds) +static __attribute__((always_inline)) ATOMICS_WAIT_RESULT_T emscripten_wasm_wait_i32(int32_t *address, int expectedValue, int64_t maxWaitNanoseconds) { return __builtin_wasm_memory_atomic_wait32(address, expectedValue, maxWaitNanoseconds); } @@ -98,7 +98,7 @@ static inline __attribute__((always_inline)) ATOMICS_WAIT_RESULT_T emscripten_wa // If the given memory address contains value 'expectedValue', puts the calling thread to sleep to wait for that address to be notified. // Returns one of the ATOMICS_WAIT_* return codes. // NOTE: This function takes in the wait value in int64_t nanosecond units. Pass in maxWaitNanoseconds = -1 (or ATOMICS_WAIT_DURATION_INFINITE) to wait infinitely long. -static inline __attribute__((always_inline)) ATOMICS_WAIT_RESULT_T emscripten_wasm_wait_i64(int64_t *address, int64_t expectedValue, int64_t maxWaitNanoseconds) +static __attribute__((always_inline)) ATOMICS_WAIT_RESULT_T emscripten_wasm_wait_i64(int64_t *address, int64_t expectedValue, int64_t maxWaitNanoseconds) { return __builtin_wasm_memory_atomic_wait64(address, expectedValue, maxWaitNanoseconds); } @@ -110,7 +110,7 @@ static inline __attribute__((always_inline)) ATOMICS_WAIT_RESULT_T emscripten_wa // Pass count == EMSCRIPTEN_NOTIFY_ALL_WAITERS to notify all waiters on the given location. // Returns the number of threads that were woken up. // Note: this function is used to notify both waiters waiting on an i32 and i64 addresses. -static inline __attribute__((always_inline)) int64_t emscripten_wasm_notify(int32_t *address, int64_t count) +static __attribute__((always_inline)) int64_t emscripten_wasm_notify(int32_t *address, int64_t count) { return __builtin_wasm_memory_atomic_notify(address, count); } From 8c94a364b1283b29c6e5c571a6224bfeadf67bc1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jukka=20Jyl=C3=A4nki?= Date: Sun, 20 Feb 2022 22:10:25 +0200 Subject: [PATCH 27/65] Add library_wasm_worker_stub.c. --- system/lib/wasm_worker/library_wasm_worker.c | 4 + .../wasm_worker/library_wasm_worker_stub.c | 113 ++++++++++++++++++ tools/system_libs.py | 17 ++- 3 files changed, 129 insertions(+), 5 deletions(-) create mode 100644 system/lib/wasm_worker/library_wasm_worker_stub.c diff --git a/system/lib/wasm_worker/library_wasm_worker.c b/system/lib/wasm_worker/library_wasm_worker.c index 32a3ac93c51b..f8d6852da993 100644 --- a/system/lib/wasm_worker/library_wasm_worker.c +++ b/system/lib/wasm_worker/library_wasm_worker.c @@ -4,6 +4,10 @@ #include #include +#ifndef __EMSCRIPTEN_WASM_WORKERS__ +#error __EMSCRIPTEN_WASM_WORKERS__ should be defined when building this file! +#endif + // Options: // #define WASM_WORKER_NO_TLS 0/1 : set to 1 to disable TLS compilation support for a small code size gain // #define STACK_OVERFLOW_CHECK 0/1/2 : set to the current stack overflow check mode diff --git a/system/lib/wasm_worker/library_wasm_worker_stub.c b/system/lib/wasm_worker/library_wasm_worker_stub.c new file mode 100644 index 000000000000..a8a257a7c46c --- /dev/null +++ b/system/lib/wasm_worker/library_wasm_worker_stub.c @@ -0,0 +1,113 @@ +#include +#include +#include +#include +#include + +#ifdef __EMSCRIPTEN_WASM_WORKERS__ +#error __EMSCRIPTEN_WASM_WORKERS__ should not be defined when building this file! +#endif + +emscripten_wasm_worker_t emscripten_create_wasm_worker_with_tls(void *stackLowestAddress, uint32_t stackSize, void *tlsAddress, uint32_t tlsSize) +{ + return 0; +} + +emscripten_wasm_worker_t emscripten_create_wasm_worker_no_tls(void *stackLowestAddress, uint32_t stackSize) +{ + return 0; +} + +emscripten_wasm_worker_t emscripten_malloc_wasm_worker(uint32_t stackSize) +{ + return 0; +} + +void emscripten_wasm_worker_sleep(int64_t nsecs) +{ +} + +void emscripten_lock_init(emscripten_lock_t *lock) +{ +} + +EM_BOOL emscripten_lock_wait_acquire(emscripten_lock_t *lock, int64_t maxWaitNanoseconds) +{ + return EM_TRUE; +} + +void emscripten_lock_waitinf_acquire(emscripten_lock_t *lock) +{ +} + +EM_BOOL emscripten_lock_busyspin_wait_acquire(emscripten_lock_t *lock, double maxWaitMilliseconds) +{ + return EM_TRUE; +} + +void emscripten_lock_busyspin_waitinf_acquire(emscripten_lock_t *lock) +{ +} + +EM_BOOL emscripten_lock_try_acquire(emscripten_lock_t *lock) +{ + return EM_TRUE; +} + +void emscripten_lock_release(emscripten_lock_t *lock) +{ +} + +void emscripten_semaphore_init(emscripten_semaphore_t *sem, int num) +{ +} + +int emscripten_semaphore_try_acquire(emscripten_semaphore_t *sem, int num) +{ + *sem -= num; + return *sem; +} + +int emscripten_semaphore_wait_acquire(emscripten_semaphore_t *sem, int num, int64_t maxWaitNanoseconds) +{ + *sem -= num; + return *sem; +} + +int emscripten_semaphore_waitinf_acquire(emscripten_semaphore_t *sem, int num) +{ + *sem -= num; + return *sem; +} + +uint32_t emscripten_semaphore_release(emscripten_semaphore_t *sem, int num) +{ + *sem += num; + return *sem - num; +} + +void emscripten_condvar_init(emscripten_condvar_t *condvar) +{ +} + +void emscripten_condvar_waitinf(emscripten_condvar_t *condvar, emscripten_lock_t *lock) +{ +} + +int emscripten_condvar_wait(emscripten_condvar_t *condvar, emscripten_lock_t *lock, int64_t maxWaitNanoseconds) +{ + return EM_TRUE; +} + +ATOMICS_WAIT_TOKEN_T emscripten_condvar_wait_async(emscripten_condvar_t *condvar, + emscripten_lock_t *lock, + void (*asyncWaitFinished)(int32_t *address, uint32_t value, ATOMICS_WAIT_RESULT_T waitResult, void *userData), + void *userData, + double maxWaitMilliseconds) +{ + return 0; +} + +void emscripten_condvar_signal(emscripten_condvar_t *condvar, int64_t numWaitersToSignal) +{ +} diff --git a/tools/system_libs.py b/tools/system_libs.py index 6282740f7b56..51e72310726f 100644 --- a/tools/system_libs.py +++ b/tools/system_libs.py @@ -1041,13 +1041,20 @@ def __init__(self, **kwargs): name = 'libwasm_workers' def get_cflags(self): - return ['-pthread', - '-D_DEBUG' if self.debug else '-Oz', - '-DSTACK_OVERFLOW_CHECK=' + ('2' if self.stack_check else '0'), - '-DWASM_WORKER_NO_TLS=' + ('0' if self.tls else '1')] + cflags = ['-pthread', + '-D_DEBUG' if self.debug else '-Oz', + '-DSTACK_OVERFLOW_CHECK=' + ('2' if self.stack_check else '0'), + '-DWASM_WORKER_NO_TLS=' + ('0' if self.tls else '1')] + if self.is_ww: + cflags += ['-sWASM_WORKERS'] + if settings.MAIN_MODULE: + cflags += ['-fPIC'] + return cflags def get_base_name(self): name = 'libwasm_workers' + if not self.is_ww: + name += '_stub' if not self.tls: name += '-notls' if self.debug: @@ -1067,7 +1074,7 @@ def get_default_variation(cls, **kwargs): def get_files(self): return files_in_path( path='system/lib/wasm_worker', - filenames=['library_wasm_worker.c']) + filenames=['library_wasm_worker.c' if self.is_ww else 'library_wasm_worker_stub.c']) class libsockets(MuslInternalLibrary, MTLibrary): From 1876f143b6a767c33c5bd59cc20d5f20a3323156 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jukka=20Jyl=C3=A4nki?= Date: Mon, 21 Feb 2022 16:17:29 +0200 Subject: [PATCH 28/65] Wasm Workers working on default runtime. --- src/library_wasm_worker.js | 13 +- src/postamble.js | 9 ++ src/preamble.js | 14 +- src/preamble_minimal.js | 15 +- src/runtime_init_memory.js | 4 +- src/shell.js | 6 +- tests/test_browser.py | 171 +++++++++----------- tests/wasm_worker/no_proxied_js_functions.c | 8 + tests/wasm_worker/thread_stack.c | 3 +- tools/minimal_runtime_shell.py | 6 +- 10 files changed, 140 insertions(+), 109 deletions(-) diff --git a/src/library_wasm_worker.js b/src/library_wasm_worker.js index 42e7d5414b7e..bcdfacf31446 100644 --- a/src/library_wasm_worker.js +++ b/src/library_wasm_worker.js @@ -80,18 +80,25 @@ mergeInto(LibraryManager.library, { #endif #endif let worker = _wasm_workers[_wasm_workers_id] = new Worker( -#if WASM_WORKERS == 2 +#if WASM_WORKERS == 2 // WASM_WORKERS=2 mode embeds .ww.js file contents into the main .js file as a Blob URL. (convenient, but not CSP security safe, since this is eval-like) __wasmWorkerBlobUrl -#else - Module['wasmWorker'] +#elif MINIMAL_RUNTIME // MINIMAL_RUNTIME has a structure where the .ww.js file is loaded from the main HTML file in parallel to all other files for best performance + Module['$wb'] // $wb="Wasm worker Blob", abbreviated since not DCEable +#else // default runtime loads the .ww.js file on demand. + locateFile('{{{ WASM_WORKER_FILE }}}') #endif ); // Craft the Module object for the Wasm Worker scope: worker.postMessage({ '$ww': _wasm_workers_id, // Signal with a non-zero value that this Worker will be a Wasm Worker, and not the main browser thread. 'wasm': Module['wasm'], +#if MINIMAL_RUNTIME 'js': Module['js'], 'mem': wasmMemory, +#else + 'js': Module['mainScriptUrlOrBlob'] || _scriptDir, + 'wasmMemory': wasmMemory, +#endif 'sb': stackLowestAddress, // sb = stack base 'sz': stackSize, // sz = stack size #if !WASM_WORKERS_NO_TLS diff --git a/src/postamble.js b/src/postamble.js index 594d2bec55fd..dd1f20c4825e 100644 --- a/src/postamble.js +++ b/src/postamble.js @@ -263,6 +263,15 @@ function run(args) { } #endif +#if WASM_WORKERS + if (ENVIRONMENT_IS_WASM_WORKER) { +#if MODULARIZE + readyPromiseResolve(Module); +#endif // MODULARIZE + return initRuntime(); + } +#endif + #if USE_PTHREADS if (ENVIRONMENT_IS_PTHREAD) { #if MODULARIZE diff --git a/src/preamble.js b/src/preamble.js index 430ac5568b23..c29c274fd485 100644 --- a/src/preamble.js +++ b/src/preamble.js @@ -48,10 +48,10 @@ if (typeof WebAssembly != 'object') { var wasmMemory; -#if USE_PTHREADS +#if SHARED_MEMORY // For sending to workers. var wasmModule; -#endif // USE_PTHREADS +#endif // SHARED_MEMORY //======================================== // Runtime essentials @@ -353,6 +353,10 @@ function initRuntime() { #endif runtimeInitialized = true; +#if WASM_WORKERS + if (ENVIRONMENT_IS_WASM_WORKER) return __wasm_worker_initializeRuntime(); +#endif + #if USE_PTHREADS if (ENVIRONMENT_IS_PTHREAD) return; #endif @@ -1064,9 +1068,13 @@ function createWasm() { // is in charge of programatically exporting them on the global object. exportAsmFunctions(exports); #endif -#if USE_PTHREADS + +#if SHARED_MEMORY // We now have the Wasm module loaded up, keep a reference to the compiled module so we can post it to the workers. wasmModule = module; +#endif + +#if USE_PTHREADS // Instantiation is synchronous in pthreads and we assert on run dependencies. if (!ENVIRONMENT_IS_PTHREAD) { #if PTHREAD_POOL_SIZE diff --git a/src/preamble_minimal.js b/src/preamble_minimal.js index 7cdc105b9467..c100226dc49b 100644 --- a/src/preamble_minimal.js +++ b/src/preamble_minimal.js @@ -46,12 +46,15 @@ Module['wasm'] = base64Decode('<<< WASM_BINARY_DATA >>>'); #include "runtime_functions.js" #include "runtime_strings.js" -var HEAP8, HEAP16, HEAP32, HEAPU8, HEAPU16, HEAPU32, HEAPF32, HEAPF64; -var wasmMemory, buffer, wasmTable; - +var HEAP8, HEAP16, HEAP32, HEAPU8, HEAPU16, HEAPU32, HEAPF32, HEAPF64, +#if WASM_BIGINT + HEAP64, HEAPU64, +#endif #if SUPPORT_BIG_ENDIAN -var HEAP_DATA_VIEW; + HEAP_DATA_VIEW, #endif + wasmMemory, buffer, wasmTable; + function updateGlobalBufferAndViews(b) { #if ASSERTIONS && SHARED_MEMORY @@ -69,6 +72,10 @@ function updateGlobalBufferAndViews(b) { HEAPU32 = new Uint32Array(b); HEAPF32 = new Float32Array(b); HEAPF64 = new Float64Array(b); +#if WASM_BIGINT + HEAP64 = new BigInt64Array(b); + HEAPU64 = new BigUint64Array(b); +#endif } #if IMPORTED_MEMORY diff --git a/src/runtime_init_memory.js b/src/runtime_init_memory.js index 44ce41b5a4d9..2d519cac4e38 100644 --- a/src/runtime_init_memory.js +++ b/src/runtime_init_memory.js @@ -34,12 +34,12 @@ if (ENVIRONMENT_IS_PTHREAD) { #else 'maximum': INITIAL_MEMORY / {{{ WASM_PAGE_SIZE }}} #endif // ALLOW_MEMORY_GROWTH -#if USE_PTHREADS +#if SHARED_MEMORY , 'shared': true #endif }); -#if USE_PTHREADS +#if SHARED_MEMORY if (!(wasmMemory.buffer instanceof SharedArrayBuffer)) { err('requested a shared WebAssembly.Memory but the returned buffer is not a SharedArrayBuffer, indicating that while the browser has SharedArrayBuffer it does not have WebAssembly threads support - you may need to set a flag'); if (ENVIRONMENT_IS_NODE) { diff --git a/src/shell.js b/src/shell.js index 021d7cf5fa62..66da99ff5677 100644 --- a/src/shell.js +++ b/src/shell.js @@ -121,7 +121,11 @@ if (Module['ENVIRONMENT']) { var ENVIRONMENT_IS_PTHREAD = Module['ENVIRONMENT_IS_PTHREAD'] || false; #endif -#if USE_PTHREADS && !MODULARIZE +#if WASM_WORKERS +var ENVIRONMENT_IS_WASM_WORKER = Module['$ww']; +#endif + +#if SHARED_MEMORY && !MODULARIZE // In MODULARIZE mode _scriptDir needs to be captured already at the very top of the page immediately when the page is parsed, so it is generated there // before the page load. In non-MODULARIZE modes generate it here. #if EXPORT_ES6 diff --git a/tests/test_browser.py b/tests/test_browser.py index bfde2fb310fa..f01ad021720c 100644 --- a/tests/test_browser.py +++ b/tests/test_browser.py @@ -104,6 +104,20 @@ def metafunc(self, with_wasm2js): return metafunc +def also_with_minimal_runtime(f): + assert callable(f) + + def metafunc(self, with_minimal_runtime): + assert self.get_setting('MINIMAL_RUNTIME') is None + if with_minimal_runtime: + self.set_setting('MINIMAL_RUNTIME', 1) + f(self) + + metafunc._parameterize = {'': (False,), + 'minimal_runtime': (True,)} + return metafunc + + def shell_with_script(shell_file, output_file, replacement): shell = read_file(path_from_root('src', shell_file)) create_file(output_file, shell.replace('{{{ SCRIPT }}}', replacement)) @@ -5155,40 +5169,38 @@ def test_system(self): self.btest_exit(test_file('system.c')) # Tests the hello_wasm_worker.c documentation example code. + @also_with_minimal_runtime def test_wasm_worker_hello(self): - self.btest(path_from_root('tests', 'wasm_worker', 'hello_wasm_worker.c'), - expected='0', - args=['-s', 'WASM_WORKERS=1', '-s', 'MINIMAL_RUNTIME=1']) + self.btest(path_from_root('tests', 'wasm_worker', 'hello_wasm_worker.c'), expected='0', args=['-sWASM_WORKERS']) # Tests Wasm Worker thread stack setup - @parameterized({ - '': ([],), - 'no_tls': (['-sWASM_WORKERS_NO_TLS=1'],), - }) - def test_wasm_worker_thread_stack(self, args): - self.btest(path_from_root('tests', 'wasm_worker', 'thread_stack.c'), - expected='0', - args=['-s', 'WASM_WORKERS=1', '-s', 'MINIMAL_RUNTIME=1'] + args) + @also_with_minimal_runtime + def test_wasm_worker_thread_stack(self): + self.btest(path_from_root('tests', 'wasm_worker', 'thread_stack.c'), expected='0', args=['-sWASM_WORKERS']) + + # Tests Wasm Worker thread stack setup without TLS support active + @also_with_minimal_runtime + def test_wasm_worker_thread_stack_no_tls(self): + self.btest(path_from_root('tests', 'wasm_worker', 'thread_stack.c'), expected='0', args=['-sWASM_WORKERS', '-sWASM_WORKERS_NO_TLS']) # Tests emscripten_malloc_wasm_worker() and emscripten_current_thread_is_wasm_worker() functions + @also_with_minimal_runtime def test_wasm_worker_malloc(self): - self.btest(path_from_root('tests', 'wasm_worker', 'malloc_wasm_worker.c'), - expected='0', - args=['-s', 'WASM_WORKERS=1', '-s', 'MINIMAL_RUNTIME=1']) + self.btest(path_from_root('tests', 'wasm_worker', 'malloc_wasm_worker.c'), expected='0', args=['-sWASM_WORKERS']) # Tests emscripten_wasm_worker_self_id() function + @also_with_minimal_runtime def test_wasm_worker_self_id(self): - self.btest(path_from_root('tests', 'wasm_worker', 'wasm_worker_self_id.c'), - expected='0', - args=['-s', 'WASM_WORKERS=1', '-s', 'MINIMAL_RUNTIME=1']) + self.btest(path_from_root('tests', 'wasm_worker', 'wasm_worker_self_id.c'), expected='0', args=['-sWASM_WORKERS']) # Tests direct Wasm Assembly .S file based TLS variables in Wasm Workers + @also_with_minimal_runtime def test_wasm_worker_tls_wasm_assembly(self): self.btest(path_from_root('tests', 'wasm_worker', 'wasm_worker_tls_wasm_assembly.c'), - expected='42', - args=['-s', 'WASM_WORKERS=1', '-s', 'MINIMAL_RUNTIME=1', path_from_root('tests', 'wasm_worker', 'wasm_worker_tls_wasm_assembly.S')]) + expected='42', args=['-sWASM_WORKERS', path_from_root('tests', 'wasm_worker', 'wasm_worker_tls_wasm_assembly.S')]) # Tests C++11 keyword thread_local for TLS in Wasm Workers + @also_with_minimal_runtime def test_wasm_worker_cpp11_thread_local(self): self.skipTest('''wasm-ld: /b/s/w/ir/cache/builder/emscripten-releases/llvm-project/llvm/include/llvm/ADT/Optional.h:199: const T &llvm::optional_detail::OptionalStorage::getValue() const & [T = unsigned int]: Assertion `hasVal' failed. PLEASE submit a bug report to https://github.com/llvm/llvm-project/issues/ and include the crash backtrace. @@ -5200,11 +5212,10 @@ def test_wasm_worker_cpp11_thread_local(self): #12 0x0000000000662f89 lld::wasm::(anonymous namespace)::LinkerDriver::linkerMain(llvm::ArrayRef) Driver.cpp:0:0 #13 0x000000000065dd5e lld::wasm::link(llvm::ArrayRef, llvm::raw_ostream&, llvm::raw_ostream&, bool, bool) (/root/emsdk/upstream/bin/wasm-ld+0x65dd5e) #14 0x000000000036f799 lldMain(int, char const**, llvm::raw_ostream&, llvm::raw_ostream&, bool) lld.cpp:0:0''') - self.btest(path_from_root('tests', 'wasm_worker', 'cpp11_thread_local.cpp'), - expected='42', - args=['-s', 'WASM_WORKERS=1', '-s', 'MINIMAL_RUNTIME=1']) + self.btest(path_from_root('tests', 'wasm_worker', 'cpp11_thread_local.cpp'), expected='42', args=['-sWASM_WORKERS']) # Tests C11 keyword _Thread_local for TLS in Wasm Workers + @also_with_minimal_runtime def test_wasm_worker_c11__Thread_local(self): self.skipTest('''wasm-ld: /b/s/w/ir/cache/builder/emscripten-releases/llvm-project/llvm/include/llvm/ADT/Optional.h:199: const T &llvm::optional_detail::OptionalStorage::getValue() const & [T = unsigned int]: Assertion `hasVal' failed. PLEASE submit a bug report to https://github.com/llvm/llvm-project/issues/ and include the crash backtrace. @@ -5217,11 +5228,10 @@ def test_wasm_worker_c11__Thread_local(self): #13 0x000000000065dd5e lld::wasm::link(llvm::ArrayRef, llvm::raw_ostream&, llvm::raw_ostream&, bool, bool) (/root/emsdk/upstream/bin/wasm-ld+0x65dd5e) #14 0x000000000036f799 lldMain(int, char const**, llvm::raw_ostream&, llvm::raw_ostream&, bool) lld.cpp:0:0''') - self.btest(path_from_root('tests', 'wasm_worker', 'c11__Thread_local.c'), - expected='42', - args=['-s', 'WASM_WORKERS=1', '-s', 'MINIMAL_RUNTIME=1', '-std=gnu11']) # Cannot test C11 - because of EM_ASM must test Gnu11. + self.btest(path_from_root('tests', 'wasm_worker', 'c11__Thread_local.c'), expected='42', args=['-sWASM_WORKERS', '-std=gnu11']) # Cannot test C11 - because of EM_ASM must test Gnu11. # Tests GCC specific extension keyword __thread for TLS in Wasm Workers + @also_with_minimal_runtime def test_wasm_worker_gcc___thread(self): self.skipTest('''wasm-ld: /b/s/w/ir/cache/builder/emscripten-releases/llvm-project/llvm/include/llvm/ADT/Optional.h:199: const T &llvm::optional_detail::OptionalStorage::getValue() const & [T = unsigned int]: Assertion `hasVal' failed. PLEASE submit a bug report to https://github.com/llvm/llvm-project/issues/ and include the crash backtrace. @@ -5233,137 +5243,114 @@ def test_wasm_worker_gcc___thread(self): #12 0x0000000000662f89 lld::wasm::(anonymous namespace)::LinkerDriver::linkerMain(llvm::ArrayRef) Driver.cpp:0:0 #13 0x000000000065dd5e lld::wasm::link(llvm::ArrayRef, llvm::raw_ostream&, llvm::raw_ostream&, bool, bool) (/root/emsdk/upstream/bin/wasm-ld+0x65dd5e) #14 0x000000000036f799 lldMain(int, char const**, llvm::raw_ostream&, llvm::raw_ostream&, bool) lld.cpp:0:0''') - self.btest(path_from_root('tests', 'wasm_worker', 'gcc___Thread.c'), - expected='42', - args=['-s', 'WASM_WORKERS=1', '-s', 'MINIMAL_RUNTIME=1', '-std=gnu11']) + self.btest(path_from_root('tests', 'wasm_worker', 'gcc___Thread.c'), expected='42', args=['-sWASM_WORKERS', '-std=gnu11']) # Tests emscripten_wasm_worker_sleep() + @also_with_minimal_runtime def test_wasm_worker_sleep(self): - self.btest(path_from_root('tests', 'wasm_worker', 'wasm_worker_sleep.c'), - expected='1', - args=['-s', 'WASM_WORKERS=1', '-s', 'MINIMAL_RUNTIME=1']) + self.btest(path_from_root('tests', 'wasm_worker', 'wasm_worker_sleep.c'), expected='1', args=['-sWASM_WORKERS']) # Tests emscripten_terminate_wasm_worker() + @also_with_minimal_runtime def test_wasm_worker_terminate(self): - self.btest(path_from_root('tests', 'wasm_worker', 'terminate_wasm_worker.c'), - expected='0', - args=['-s', 'WASM_WORKERS=1', '-s', 'MINIMAL_RUNTIME=1']) + self.btest(path_from_root('tests', 'wasm_worker', 'terminate_wasm_worker.c'), expected='0', args=['-sWASM_WORKERS']) # Tests emscripten_terminate_all_wasm_workers() + @also_with_minimal_runtime def test_wasm_worker_terminate_all(self): - self.btest(path_from_root('tests', 'wasm_worker', 'terminate_all_wasm_workers.c'), - expected='0', - args=['-s', 'WASM_WORKERS=1', '-s', 'MINIMAL_RUNTIME=1']) + self.btest(path_from_root('tests', 'wasm_worker', 'terminate_all_wasm_workers.c'), expected='0', args=['-sWASM_WORKERS']) # Tests emscripten_wasm_worker_post_function_*() API + @also_with_minimal_runtime def test_wasm_worker_post_function(self): - self.btest(path_from_root('tests', 'wasm_worker', 'post_function.c'), - expected='8', - args=['-s', 'WASM_WORKERS=1', '-s', 'MINIMAL_RUNTIME=1']) + self.btest(path_from_root('tests', 'wasm_worker', 'post_function.c'), expected='8', args=['-sWASM_WORKERS']) # Tests emscripten_wasm_worker_post_function_*() API and EMSCRIPTEN_WASM_WORKER_ID_PARENT # to send a message back from Worker to its parent thread. + @also_with_minimal_runtime def test_wasm_worker_post_function_to_main_thread(self): - self.btest(path_from_root('tests', 'wasm_worker', 'post_function_to_main_thread.c'), - expected='10', - args=['-s', 'WASM_WORKERS=1', '-s', 'MINIMAL_RUNTIME=1']) + self.btest(path_from_root('tests', 'wasm_worker', 'post_function_to_main_thread.c'), expected='10', args=['-sWASM_WORKERS']) # Tests emscripten_navigator_hardware_concurrency() and emscripten_atomics_is_lock_free() + @also_with_minimal_runtime def test_wasm_worker_hardware_concurrency_is_lock_free(self): - self.btest(path_from_root('tests', 'wasm_worker', 'hardware_concurrency_is_lock_free.c'), - expected='0', - args=['-s', 'WASM_WORKERS=1', '-s', 'MINIMAL_RUNTIME=1']) + self.btest(path_from_root('tests', 'wasm_worker', 'hardware_concurrency_is_lock_free.c'), expected='0', args=['-sWASM_WORKERS']) # Tests emscripten_wasm_wait_i32() and emscripten_wasm_notify() functions. + @also_with_minimal_runtime def test_wasm_worker_wait32_notify(self): - self.btest(path_from_root('tests', 'wasm_worker', 'wait32_notify.c'), - expected='2', - args=['-s', 'WASM_WORKERS=1', '-s', 'MINIMAL_RUNTIME=1']) + self.btest(path_from_root('tests', 'wasm_worker', 'wait32_notify.c'), expected='2', args=['-sWASM_WORKERS']) # Tests emscripten_wasm_wait_i64() and emscripten_wasm_notify() functions. + @also_with_minimal_runtime def test_wasm_worker_wait64_notify(self): - self.btest(path_from_root('tests', 'wasm_worker', 'wait64_notify.c'), - expected='2', - args=['-s', 'WASM_WORKERS=1', '-s', 'MINIMAL_RUNTIME=1']) + self.btest(path_from_root('tests', 'wasm_worker', 'wait64_notify.c'), expected='2', args=['-sWASM_WORKERS']) # Tests emscripten_atomic_wait_async() function. + @also_with_minimal_runtime def test_wasm_worker_wait_async(self): - self.btest(path_from_root('tests', 'wasm_worker', 'wait_async.c'), - expected='0', - args=['-s', 'WASM_WORKERS=1', '-s', 'MINIMAL_RUNTIME=1']) + self.btest(path_from_root('tests', 'wasm_worker', 'wait_async.c'), expected='0', args=['-sWASM_WORKERS']) # Tests emscripten_atomic_cancel_wait_async() function. + @also_with_minimal_runtime def test_wasm_worker_cancel_wait_async(self): - self.btest(path_from_root('tests', 'wasm_worker', 'cancel_wait_async.c'), - expected='1', - args=['-s', 'WASM_WORKERS=1', '-s', 'MINIMAL_RUNTIME=1']) + self.btest(path_from_root('tests', 'wasm_worker', 'cancel_wait_async.c'), expected='1', args=['-sWASM_WORKERS']) # Tests emscripten_atomic_cancel_all_wait_asyncs() function. + @also_with_minimal_runtime def test_wasm_worker_cancel_all_wait_asyncs(self): - self.btest(path_from_root('tests', 'wasm_worker', 'cancel_all_wait_asyncs.c'), - expected='1', - args=['-s', 'WASM_WORKERS=1', '-s', 'MINIMAL_RUNTIME=1']) + self.btest(path_from_root('tests', 'wasm_worker', 'cancel_all_wait_asyncs.c'), expected='1', args=['-sWASM_WORKERS']) # Tests emscripten_atomic_cancel_all_wait_asyncs_at_address() function. + @also_with_minimal_runtime def test_wasm_worker_cancel_all_wait_asyncs_at_address(self): - self.btest(path_from_root('tests', 'wasm_worker', 'cancel_all_wait_asyncs_at_address.c'), - expected='1', - args=['-s', 'WASM_WORKERS=1', '-s', 'MINIMAL_RUNTIME=1']) + self.btest(path_from_root('tests', 'wasm_worker', 'cancel_all_wait_asyncs_at_address.c'), expected='1', args=['-sWASM_WORKERS']) # Tests emscripten_lock_init(), emscripten_lock_waitinf_acquire() and emscripten_lock_release() + @also_with_minimal_runtime def test_wasm_worker_lock_waitinf(self): - self.btest(path_from_root('tests', 'wasm_worker', 'lock_waitinf_acquire.c'), - expected='4000', - args=['-s', 'WASM_WORKERS=1', '-s', 'MINIMAL_RUNTIME=1']) + self.btest(path_from_root('tests', 'wasm_worker', 'lock_waitinf_acquire.c'), expected='4000', args=['-sWASM_WORKERS']) # Tests emscripten_lock_wait_acquire() and emscripten_lock_try_acquire() in Worker. + @also_with_minimal_runtime def test_wasm_worker_lock_wait(self): - self.btest(path_from_root('tests', 'wasm_worker', 'lock_wait_acquire.c'), - expected='0', - args=['-s', 'WASM_WORKERS=1', '-s', 'MINIMAL_RUNTIME=1']) + self.btest(path_from_root('tests', 'wasm_worker', 'lock_wait_acquire.c'), expected='0', args=['-sWASM_WORKERS']) # Tests emscripten_lock_wait_acquire() between two Wasm Workers. + @also_with_minimal_runtime def test_wasm_worker_lock_wait2(self): - self.btest(path_from_root('tests', 'wasm_worker', 'lock_wait_acquire2.c'), - expected='0', - args=['-s', 'WASM_WORKERS=1', '-s', 'MINIMAL_RUNTIME=1']) + self.btest(path_from_root('tests', 'wasm_worker', 'lock_wait_acquire2.c'), expected='0', args=['-sWASM_WORKERS']) # Tests emscripten_lock_async_acquire() function. + @also_with_minimal_runtime def test_wasm_worker_lock_async_acquire(self): - self.btest(path_from_root('tests', 'wasm_worker', 'lock_async_acquire.c'), - expected='0', - args=['-s', 'WASM_WORKERS=1', '-s', 'MINIMAL_RUNTIME=1']) + self.btest(path_from_root('tests', 'wasm_worker', 'lock_async_acquire.c'), expected='0', args=['-sWASM_WORKERS']) # Tests emscripten_lock_busyspin_wait_acquire() in Worker and main thread. + @also_with_minimal_runtime def test_wasm_worker_lock_busyspin_wait(self): - self.btest(path_from_root('tests', 'wasm_worker', 'lock_busyspin_wait_acquire.c'), - expected='0', - args=['-s', 'WASM_WORKERS=1', '-s', 'MINIMAL_RUNTIME=1']) + self.btest(path_from_root('tests', 'wasm_worker', 'lock_busyspin_wait_acquire.c'), expected='0', args=['-sWASM_WORKERS']) # Tests emscripten_lock_busyspin_waitinf_acquire() in Worker and main thread. + @also_with_minimal_runtime def test_wasm_worker_lock_busyspin_waitinf(self): - self.btest(path_from_root('tests', 'wasm_worker', 'lock_busyspin_waitinf_acquire.c'), - expected='1', - args=['-s', 'WASM_WORKERS=1', '-s', 'MINIMAL_RUNTIME=1']) + self.btest(path_from_root('tests', 'wasm_worker', 'lock_busyspin_waitinf_acquire.c'), expected='1', args=['-sWASM_WORKERS']) # Tests that proxied JS functions cannot be called from Wasm Workers + @also_with_minimal_runtime def test_wasm_worker_no_proxied_js_functions(self): - self.btest(path_from_root('tests', 'wasm_worker', 'no_proxied_js_functions.c'), - expected='0', - args=['--js-library', path_from_root('tests', 'wasm_worker', 'no_proxied_js_functions.js'), - '-s', 'WASM_WORKERS=1', '-s', 'MINIMAL_RUNTIME=1', '-s', 'ASSERTIONS=1']) + self.btest(path_from_root('tests', 'wasm_worker', 'no_proxied_js_functions.c'), expected='0', + args=['--js-library', path_from_root('tests', 'wasm_worker', 'no_proxied_js_functions.js'), '-sWASM_WORKERS', '-sASSERTIONS']) # Tests emscripten_semaphore_init(), emscripten_semaphore_waitinf_acquire() and emscripten_semaphore_release() + @also_with_minimal_runtime def test_wasm_worker_semaphore_waitinf_acquire(self): - self.btest(path_from_root('tests', 'wasm_worker', 'semaphore_waitinf_acquire.c'), - expected='0', - args=['-s', 'WASM_WORKERS=1', '-s', 'MINIMAL_RUNTIME=1']) + self.btest(path_from_root('tests', 'wasm_worker', 'semaphore_waitinf_acquire.c'), expected='0', args=['-sWASM_WORKERS']) # Tests emscripten_semaphore_try_acquire() on the main thread + @also_with_minimal_runtime def test_wasm_worker_semaphore_try_acquire(self): - self.btest(path_from_root('tests', 'wasm_worker', 'semaphore_try_acquire.c'), - expected='0', - args=['-s', 'WASM_WORKERS=1', '-s', 'MINIMAL_RUNTIME=1']) + self.btest(path_from_root('tests', 'wasm_worker', 'semaphore_try_acquire.c'), expected='0', args=['-sWASM_WORKERS']) @no_firefox('no 4GB support yet') @require_v8 diff --git a/tests/wasm_worker/no_proxied_js_functions.c b/tests/wasm_worker/no_proxied_js_functions.c index ce6e0a9a4743..afd2b50f40dc 100644 --- a/tests/wasm_worker/no_proxied_js_functions.c +++ b/tests/wasm_worker/no_proxied_js_functions.c @@ -9,6 +9,14 @@ void proxied_js_function(void); int should_throw(void(*func)()) { int threw = EM_ASM_INT({ + // Patch over assert() so that it does not abort execution on assert failure, but instead + // throws a catchable exception. + assert = function(condition, text) { + if (!condition) { + throw 'Assertion failed' + (text ? ": " + text : ""); + } + }; + try { dynCall('v', $0); } catch(e) { diff --git a/tests/wasm_worker/thread_stack.c b/tests/wasm_worker/thread_stack.c index d50e16c5dc1c..1b6fdac51d52 100644 --- a/tests/wasm_worker/thread_stack.c +++ b/tests/wasm_worker/thread_stack.c @@ -13,7 +13,8 @@ volatile int threadsOk = 0; void test_stack(int i) { - EM_ASM(console.log(`In thread ${$0}, stack base=0x${$1.toString(16)}, end=0x${$2.toString(16)}`), i, emscripten_stack_get_base(), emscripten_stack_get_end()); + EM_ASM(console.log(`In thread ${$0}, stack low addr=0x${$1.toString(16)}, emscripten_stack_get_base()=0x${$2.toString(16)}, emscripten_stack_get_end()=0x${$3.toString(16)}, THREAD_STACK_SIZE=0x${$4.toString(16)}`), + i, thread_stack[i], emscripten_stack_get_base(), emscripten_stack_get_end(), THREAD_STACK_SIZE); assert(emscripten_stack_get_base() == (uintptr_t)thread_stack[i] + THREAD_STACK_SIZE); assert(emscripten_stack_get_end() == (uintptr_t)thread_stack[i]); diff --git a/tools/minimal_runtime_shell.py b/tools/minimal_runtime_shell.py index 73aa5f165343..9db73878b725 100644 --- a/tools/minimal_runtime_shell.py +++ b/tools/minimal_runtime_shell.py @@ -65,12 +65,12 @@ def generate_minimal_runtime_load_statement(target_basename): # Download wasm_worker file if settings.WASM_WORKERS: if settings.MODULARIZE: - if settings.WASM_WORKERS == 1: - modularize_imports += ['wasmWorker: URL.createObjectURL(new Blob([r[%d]], { type: \'application/javascript\' }))' % len(files_to_load)] + if settings.WASM_WORKERS == 1: # '$wb': Wasm Worker Blob + modularize_imports += ['$wb: URL.createObjectURL(new Blob([r[%d]], { type: \'application/javascript\' }))' % len(files_to_load)] modularize_imports += ['js: js'] else: if settings.WASM_WORKERS == 1: - then_statements += ['%s.wasmWorker = URL.createObjectURL(new Blob([r[%d]], { type: \'application/javascript\' }));' % (settings.EXPORT_NAME, len(files_to_load))] + then_statements += ['%s.$wb = URL.createObjectURL(new Blob([r[%d]], { type: \'application/javascript\' }));' % (settings.EXPORT_NAME, len(files_to_load))] if download_wasm and settings.WASM_WORKERS == 1: files_to_load += ["binary('%s')" % (target_basename + '.ww.js')] From 3fa9bacd70ff3ab8d3d41cfaf1d93f7cf996ba8b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jukka=20Jyl=C3=A4nki?= Date: Mon, 21 Feb 2022 17:22:18 +0200 Subject: [PATCH 29/65] flake --- tests/test_browser.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_browser.py b/tests/test_browser.py index f01ad021720c..238f7170ee62 100644 --- a/tests/test_browser.py +++ b/tests/test_browser.py @@ -5340,7 +5340,7 @@ def test_wasm_worker_lock_busyspin_waitinf(self): @also_with_minimal_runtime def test_wasm_worker_no_proxied_js_functions(self): self.btest(path_from_root('tests', 'wasm_worker', 'no_proxied_js_functions.c'), expected='0', - args=['--js-library', path_from_root('tests', 'wasm_worker', 'no_proxied_js_functions.js'), '-sWASM_WORKERS', '-sASSERTIONS']) + args=['--js-library', path_from_root('tests', 'wasm_worker', 'no_proxied_js_functions.js'), '-sWASM_WORKERS', '-sASSERTIONS']) # Tests emscripten_semaphore_init(), emscripten_semaphore_waitinf_acquire() and emscripten_semaphore_release() @also_with_minimal_runtime From fdb8c92f0aaada031013f7a41b0e66a38794a6a9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jukka=20Jyl=C3=A4nki?= Date: Tue, 22 Feb 2022 00:05:20 +0200 Subject: [PATCH 30/65] Disable most wasm workers tests to debug CI --- tests/test_browser.py | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/tests/test_browser.py b/tests/test_browser.py index 238f7170ee62..eaa596caffdb 100644 --- a/tests/test_browser.py +++ b/tests/test_browser.py @@ -5176,32 +5176,38 @@ def test_wasm_worker_hello(self): # Tests Wasm Worker thread stack setup @also_with_minimal_runtime def test_wasm_worker_thread_stack(self): + self.skipTest('skipped to debug CI') self.btest(path_from_root('tests', 'wasm_worker', 'thread_stack.c'), expected='0', args=['-sWASM_WORKERS']) # Tests Wasm Worker thread stack setup without TLS support active @also_with_minimal_runtime def test_wasm_worker_thread_stack_no_tls(self): + self.skipTest('skipped to debug CI') self.btest(path_from_root('tests', 'wasm_worker', 'thread_stack.c'), expected='0', args=['-sWASM_WORKERS', '-sWASM_WORKERS_NO_TLS']) # Tests emscripten_malloc_wasm_worker() and emscripten_current_thread_is_wasm_worker() functions @also_with_minimal_runtime def test_wasm_worker_malloc(self): + self.skipTest('skipped to debug CI') self.btest(path_from_root('tests', 'wasm_worker', 'malloc_wasm_worker.c'), expected='0', args=['-sWASM_WORKERS']) # Tests emscripten_wasm_worker_self_id() function @also_with_minimal_runtime def test_wasm_worker_self_id(self): + self.skipTest('skipped to debug CI') self.btest(path_from_root('tests', 'wasm_worker', 'wasm_worker_self_id.c'), expected='0', args=['-sWASM_WORKERS']) # Tests direct Wasm Assembly .S file based TLS variables in Wasm Workers @also_with_minimal_runtime def test_wasm_worker_tls_wasm_assembly(self): + self.skipTest('skipped to debug CI') self.btest(path_from_root('tests', 'wasm_worker', 'wasm_worker_tls_wasm_assembly.c'), expected='42', args=['-sWASM_WORKERS', path_from_root('tests', 'wasm_worker', 'wasm_worker_tls_wasm_assembly.S')]) # Tests C++11 keyword thread_local for TLS in Wasm Workers @also_with_minimal_runtime def test_wasm_worker_cpp11_thread_local(self): + self.skipTest('skipped to debug CI') self.skipTest('''wasm-ld: /b/s/w/ir/cache/builder/emscripten-releases/llvm-project/llvm/include/llvm/ADT/Optional.h:199: const T &llvm::optional_detail::OptionalStorage::getValue() const & [T = unsigned int]: Assertion `hasVal' failed. PLEASE submit a bug report to https://github.com/llvm/llvm-project/issues/ and include the crash backtrace. Stack dump: @@ -5217,6 +5223,7 @@ def test_wasm_worker_cpp11_thread_local(self): # Tests C11 keyword _Thread_local for TLS in Wasm Workers @also_with_minimal_runtime def test_wasm_worker_c11__Thread_local(self): + self.skipTest('skipped to debug CI') self.skipTest('''wasm-ld: /b/s/w/ir/cache/builder/emscripten-releases/llvm-project/llvm/include/llvm/ADT/Optional.h:199: const T &llvm::optional_detail::OptionalStorage::getValue() const & [T = unsigned int]: Assertion `hasVal' failed. PLEASE submit a bug report to https://github.com/llvm/llvm-project/issues/ and include the crash backtrace. Stack dump: @@ -5233,6 +5240,7 @@ def test_wasm_worker_c11__Thread_local(self): # Tests GCC specific extension keyword __thread for TLS in Wasm Workers @also_with_minimal_runtime def test_wasm_worker_gcc___thread(self): + self.skipTest('skipped to debug CI') self.skipTest('''wasm-ld: /b/s/w/ir/cache/builder/emscripten-releases/llvm-project/llvm/include/llvm/ADT/Optional.h:199: const T &llvm::optional_detail::OptionalStorage::getValue() const & [T = unsigned int]: Assertion `hasVal' failed. PLEASE submit a bug report to https://github.com/llvm/llvm-project/issues/ and include the crash backtrace. Stack dump: @@ -5248,108 +5256,129 @@ def test_wasm_worker_gcc___thread(self): # Tests emscripten_wasm_worker_sleep() @also_with_minimal_runtime def test_wasm_worker_sleep(self): + self.skipTest('skipped to debug CI') self.btest(path_from_root('tests', 'wasm_worker', 'wasm_worker_sleep.c'), expected='1', args=['-sWASM_WORKERS']) # Tests emscripten_terminate_wasm_worker() @also_with_minimal_runtime def test_wasm_worker_terminate(self): + self.skipTest('skipped to debug CI') self.btest(path_from_root('tests', 'wasm_worker', 'terminate_wasm_worker.c'), expected='0', args=['-sWASM_WORKERS']) # Tests emscripten_terminate_all_wasm_workers() @also_with_minimal_runtime def test_wasm_worker_terminate_all(self): + self.skipTest('skipped to debug CI') self.btest(path_from_root('tests', 'wasm_worker', 'terminate_all_wasm_workers.c'), expected='0', args=['-sWASM_WORKERS']) # Tests emscripten_wasm_worker_post_function_*() API @also_with_minimal_runtime def test_wasm_worker_post_function(self): + self.skipTest('skipped to debug CI') self.btest(path_from_root('tests', 'wasm_worker', 'post_function.c'), expected='8', args=['-sWASM_WORKERS']) # Tests emscripten_wasm_worker_post_function_*() API and EMSCRIPTEN_WASM_WORKER_ID_PARENT # to send a message back from Worker to its parent thread. @also_with_minimal_runtime def test_wasm_worker_post_function_to_main_thread(self): + self.skipTest('skipped to debug CI') self.btest(path_from_root('tests', 'wasm_worker', 'post_function_to_main_thread.c'), expected='10', args=['-sWASM_WORKERS']) # Tests emscripten_navigator_hardware_concurrency() and emscripten_atomics_is_lock_free() @also_with_minimal_runtime def test_wasm_worker_hardware_concurrency_is_lock_free(self): + self.skipTest('skipped to debug CI') self.btest(path_from_root('tests', 'wasm_worker', 'hardware_concurrency_is_lock_free.c'), expected='0', args=['-sWASM_WORKERS']) # Tests emscripten_wasm_wait_i32() and emscripten_wasm_notify() functions. @also_with_minimal_runtime def test_wasm_worker_wait32_notify(self): + self.skipTest('skipped to debug CI') self.btest(path_from_root('tests', 'wasm_worker', 'wait32_notify.c'), expected='2', args=['-sWASM_WORKERS']) # Tests emscripten_wasm_wait_i64() and emscripten_wasm_notify() functions. @also_with_minimal_runtime def test_wasm_worker_wait64_notify(self): + self.skipTest('skipped to debug CI') self.btest(path_from_root('tests', 'wasm_worker', 'wait64_notify.c'), expected='2', args=['-sWASM_WORKERS']) # Tests emscripten_atomic_wait_async() function. @also_with_minimal_runtime def test_wasm_worker_wait_async(self): + self.skipTest('skipped to debug CI') self.btest(path_from_root('tests', 'wasm_worker', 'wait_async.c'), expected='0', args=['-sWASM_WORKERS']) # Tests emscripten_atomic_cancel_wait_async() function. @also_with_minimal_runtime def test_wasm_worker_cancel_wait_async(self): + self.skipTest('skipped to debug CI') self.btest(path_from_root('tests', 'wasm_worker', 'cancel_wait_async.c'), expected='1', args=['-sWASM_WORKERS']) # Tests emscripten_atomic_cancel_all_wait_asyncs() function. @also_with_minimal_runtime def test_wasm_worker_cancel_all_wait_asyncs(self): + self.skipTest('skipped to debug CI') self.btest(path_from_root('tests', 'wasm_worker', 'cancel_all_wait_asyncs.c'), expected='1', args=['-sWASM_WORKERS']) # Tests emscripten_atomic_cancel_all_wait_asyncs_at_address() function. @also_with_minimal_runtime def test_wasm_worker_cancel_all_wait_asyncs_at_address(self): + self.skipTest('skipped to debug CI') self.btest(path_from_root('tests', 'wasm_worker', 'cancel_all_wait_asyncs_at_address.c'), expected='1', args=['-sWASM_WORKERS']) # Tests emscripten_lock_init(), emscripten_lock_waitinf_acquire() and emscripten_lock_release() @also_with_minimal_runtime def test_wasm_worker_lock_waitinf(self): + self.skipTest('skipped to debug CI') self.btest(path_from_root('tests', 'wasm_worker', 'lock_waitinf_acquire.c'), expected='4000', args=['-sWASM_WORKERS']) # Tests emscripten_lock_wait_acquire() and emscripten_lock_try_acquire() in Worker. @also_with_minimal_runtime def test_wasm_worker_lock_wait(self): + self.skipTest('skipped to debug CI') self.btest(path_from_root('tests', 'wasm_worker', 'lock_wait_acquire.c'), expected='0', args=['-sWASM_WORKERS']) # Tests emscripten_lock_wait_acquire() between two Wasm Workers. @also_with_minimal_runtime def test_wasm_worker_lock_wait2(self): + self.skipTest('skipped to debug CI') self.btest(path_from_root('tests', 'wasm_worker', 'lock_wait_acquire2.c'), expected='0', args=['-sWASM_WORKERS']) # Tests emscripten_lock_async_acquire() function. @also_with_minimal_runtime def test_wasm_worker_lock_async_acquire(self): + self.skipTest('skipped to debug CI') self.btest(path_from_root('tests', 'wasm_worker', 'lock_async_acquire.c'), expected='0', args=['-sWASM_WORKERS']) # Tests emscripten_lock_busyspin_wait_acquire() in Worker and main thread. @also_with_minimal_runtime def test_wasm_worker_lock_busyspin_wait(self): + self.skipTest('skipped to debug CI') self.btest(path_from_root('tests', 'wasm_worker', 'lock_busyspin_wait_acquire.c'), expected='0', args=['-sWASM_WORKERS']) # Tests emscripten_lock_busyspin_waitinf_acquire() in Worker and main thread. @also_with_minimal_runtime def test_wasm_worker_lock_busyspin_waitinf(self): + self.skipTest('skipped to debug CI') self.btest(path_from_root('tests', 'wasm_worker', 'lock_busyspin_waitinf_acquire.c'), expected='1', args=['-sWASM_WORKERS']) # Tests that proxied JS functions cannot be called from Wasm Workers @also_with_minimal_runtime def test_wasm_worker_no_proxied_js_functions(self): + self.skipTest('skipped to debug CI') self.btest(path_from_root('tests', 'wasm_worker', 'no_proxied_js_functions.c'), expected='0', args=['--js-library', path_from_root('tests', 'wasm_worker', 'no_proxied_js_functions.js'), '-sWASM_WORKERS', '-sASSERTIONS']) # Tests emscripten_semaphore_init(), emscripten_semaphore_waitinf_acquire() and emscripten_semaphore_release() @also_with_minimal_runtime def test_wasm_worker_semaphore_waitinf_acquire(self): + self.skipTest('skipped to debug CI') self.btest(path_from_root('tests', 'wasm_worker', 'semaphore_waitinf_acquire.c'), expected='0', args=['-sWASM_WORKERS']) # Tests emscripten_semaphore_try_acquire() on the main thread @also_with_minimal_runtime def test_wasm_worker_semaphore_try_acquire(self): + self.skipTest('skipped to debug CI') self.btest(path_from_root('tests', 'wasm_worker', 'semaphore_try_acquire.c'), expected='0', args=['-sWASM_WORKERS']) @no_firefox('no 4GB support yet') From 37fba96deb2109d78683554ef38fa675ef68d7f3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jukka=20Jyl=C3=A4nki?= Date: Tue, 22 Feb 2022 10:41:33 +0200 Subject: [PATCH 31/65] Fix non-minimal runtime wasm workers startup. Add test for WASM_WORKERS=2 build mode. --- src/library_wasm_worker.js | 6 ++++-- src/parseTools.js | 5 ++++- src/preamble.js | 10 +++++++++- src/wasm_worker.js | 3 +++ tests/test_browser.py | 6 ++++++ 5 files changed, 26 insertions(+), 4 deletions(-) diff --git a/src/library_wasm_worker.js b/src/library_wasm_worker.js index bcdfacf31446..f8420b92bf47 100644 --- a/src/library_wasm_worker.js +++ b/src/library_wasm_worker.js @@ -1,5 +1,6 @@ {{{ (function() { global.captureModuleArg = function() { return MODULARIZE ? '' : 'self.Module=d;'; }; return null; })(); }}} {{{ (function() { global.instantiateModule = function() { return MODULARIZE ? `${EXPORT_NAME}(d);` : ''; }; return null; })(); }}} +{{{ (function() { global.instantiateWasm = function() { return MINIMAL_RUNTIME ? '' : 'd[`instantiateWasm`]=(i,r)=>{var n=new WebAssembly.Instance(d[`wasm`],i);r(n,d[`wasm`]);return n.exports};'; }; return null; })(); }}} mergeInto(LibraryManager.library, { wasm_workers: {}, @@ -59,7 +60,7 @@ mergeInto(LibraryManager.library, { #if WASM_WORKERS == 2 // In WASM_WORKERS == 2 build mode, we create the Wasm Worker global scope script from a string bundled in the main application JS file. This simplifies the number of deployed JS files with the app, // but has a downside that the generated build output will no longer be csp-eval compliant. https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/script-src#unsafe_eval_expressions - _wasmWorkerBlobUrl: "URL.createObjectURL(new Blob(['onmessage=function(d){onmessage=null;d=d.data;{{{ captureModuleArg() }}}importScripts(d.js);{{{ instantiateModule() }}}d.wasm=d.mem=d.js=0;}'],{type:'application/javascript'}))", + _wasmWorkerBlobUrl: "URL.createObjectURL(new Blob(['onmessage=function(d){onmessage=null;d=d.data;{{{ captureModuleArg() }}}{{{ instantiateWasm() }}}importScripts(d.js);{{{ instantiateModule() }}}d.wasm=d.mem=d.js=0;}'],{type:'application/javascript'}))", #endif _emscripten_create_wasm_worker_with_tls__deps: ['wasm_workers', 'wasm_workers_id', '_wasm_worker_appendToQueue', '_wasm_worker_runPostMessage' @@ -91,11 +92,12 @@ mergeInto(LibraryManager.library, { // Craft the Module object for the Wasm Worker scope: worker.postMessage({ '$ww': _wasm_workers_id, // Signal with a non-zero value that this Worker will be a Wasm Worker, and not the main browser thread. - 'wasm': Module['wasm'], #if MINIMAL_RUNTIME + 'wasm': Module['wasm'], 'js': Module['js'], 'mem': wasmMemory, #else + 'wasm': wasmModule, 'js': Module['mainScriptUrlOrBlob'] || _scriptDir, 'wasmMemory': wasmMemory, #endif diff --git a/src/parseTools.js b/src/parseTools.js index a9cfe8afb3f2..39ff559e3478 100644 --- a/src/parseTools.js +++ b/src/parseTools.js @@ -938,7 +938,10 @@ function modifyFunction(text, func) { } function runOnMainThread(text) { - if (USE_PTHREADS) { + assert(!WASM_WORKERS || !USE_PTHREADS); // USE_PTHREADS and WASM_WORKERS not supported simultaneously. + if (WASM_WORKERS) { + return 'if (!ENVIRONMENT_IS_WASM_WORKER) { ' + text + ' }'; + } else if (USE_PTHREADS) { return 'if (!ENVIRONMENT_IS_PTHREAD) { ' + text + ' }'; } else { return text; diff --git a/src/preamble.js b/src/preamble.js index c29c274fd485..b966c152d601 100644 --- a/src/preamble.js +++ b/src/preamble.js @@ -1074,6 +1074,9 @@ function createWasm() { wasmModule = module; #endif +#if WASM_WORKERS + if (!ENVIRONMENT_IS_WASM_WORKER) { +#endif #if USE_PTHREADS // Instantiation is synchronous in pthreads and we assert on run dependencies. if (!ENVIRONMENT_IS_PTHREAD) { @@ -1095,7 +1098,11 @@ function createWasm() { } #else // singlethreaded build: removeRunDependency('wasm-instantiate'); +#endif // ~USE_PTHREADS +#if WASM_WORKERS + } #endif + } // we can't run yet (except in a pthread, where we have a custom sync instantiator) {{{ runOnMainThread("addRunDependency('wasm-instantiate');") }}} @@ -1124,7 +1131,7 @@ function createWasm() { assert(Module === trueModule, 'the Module object should not be replaced during async compilation - perhaps the order of HTML elements is wrong?'); trueModule = null; #endif -#if USE_PTHREADS || RELOCATABLE +#if SHARED_MEMORY || RELOCATABLE receiveInstance(result['instance'], result['module']); #else // TODO: Due to Closure regression https://github.com/google/closure-compiler/issues/3193, the above line no longer optimizes out down to the following line. @@ -1245,6 +1252,7 @@ function createWasm() { // User shell pages can write their own Module.instantiateWasm = function(imports, successCallback) callback // to manually instantiate the Wasm module themselves. This allows pages to run the instantiation parallel // to any other async startup actions they are performing. + // Also pthreads and wasm workers initialize the wasm instance through this path. if (Module['instantiateWasm']) { #if USE_OFFSET_CONVERTER #if ASSERTIONS && USE_PTHREADS diff --git a/src/wasm_worker.js b/src/wasm_worker.js index 81e70a771a0f..8702f2595d2b 100644 --- a/src/wasm_worker.js +++ b/src/wasm_worker.js @@ -10,6 +10,9 @@ onmessage = function(d) { d = d.data; #if !MODULARIZE self.{{{ EXPORT_NAME }}} = d; +#endif +#if !MINIMAL_RUNTIME + d['instantiateWasm'] = (info, receiveInstance) => { var instance = new WebAssembly.Instance(d['wasm'], info); receiveInstance(instance, d['wasm']); return instance.exports; } #endif importScripts(d.js); #if MODULARIZE diff --git a/tests/test_browser.py b/tests/test_browser.py index eaa596caffdb..2a61f33fc3ae 100644 --- a/tests/test_browser.py +++ b/tests/test_browser.py @@ -5171,8 +5171,14 @@ def test_system(self): # Tests the hello_wasm_worker.c documentation example code. @also_with_minimal_runtime def test_wasm_worker_hello(self): + self.skipTest('skipped to debug CI') self.btest(path_from_root('tests', 'wasm_worker', 'hello_wasm_worker.c'), expected='0', args=['-sWASM_WORKERS']) + # Tests the WASM_WORKERS=2 build mode, which embeds the Wasm Worker bootstrap JS script file to the main JS file. + @also_with_minimal_runtime + def test_wasm_worker_embedded(self): + self.btest(path_from_root('tests', 'wasm_worker', 'hello_wasm_worker.c'), expected='0', args=['-sWASM_WORKERS=2']) + # Tests Wasm Worker thread stack setup @also_with_minimal_runtime def test_wasm_worker_thread_stack(self): From a9e47d62cdd35ebb4e1f2ed02db10feb552aeff0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jukka=20Jyl=C3=A4nki?= Date: Tue, 22 Feb 2022 12:17:41 +0200 Subject: [PATCH 32/65] Simplify in MINIMAL_RUNTIME preamble assignment for wasm maximum memory. --- src/preamble_minimal.js | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/src/preamble_minimal.js b/src/preamble_minimal.js index c100226dc49b..3a9a0b7ba385 100644 --- a/src/preamble_minimal.js +++ b/src/preamble_minimal.js @@ -82,12 +82,6 @@ function updateGlobalBufferAndViews(b) { #if USE_PTHREADS if (!ENVIRONMENT_IS_PTHREAD) { #endif -#if ALLOW_MEMORY_GROWTH && MAXIMUM_MEMORY != FOUR_GB - var wasmMaximumMemory = {{{ MAXIMUM_MEMORY >>> 16 }}}; -#else - var wasmMaximumMemory = {{{ INITIAL_MEMORY >>> 16}}}; -#endif - wasmMemory = #if WASM_WORKERS Module['mem'] || @@ -95,7 +89,7 @@ if (!ENVIRONMENT_IS_PTHREAD) { new WebAssembly.Memory({ 'initial': {{{ INITIAL_MEMORY >>> 16 }}} #if SHARED_MEMORY || !ALLOW_MEMORY_GROWTH || MAXIMUM_MEMORY != FOUR_GB - , 'maximum': wasmMaximumMemory + , 'maximum': {{{ (ALLOW_MEMORY_GROWTH && MAXIMUM_MEMORY != FOUR_GB ? MAXIMUM_MEMORY : INITIAL_MEMORY) >>> 16 }}} #endif #if SHARED_MEMORY , 'shared': true From 4091a56652d0a76a6a6dac7adde30661a85d2422 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jukka=20Jyl=C3=A4nki?= Date: Tue, 22 Feb 2022 12:22:14 +0200 Subject: [PATCH 33/65] Fix USE_PTHREADS+WASM_WORKERS line. --- src/shell_minimal.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/shell_minimal.js b/src/shell_minimal.js index 0c957daf6bfc..da40624edd11 100644 --- a/src/shell_minimal.js +++ b/src/shell_minimal.js @@ -122,7 +122,10 @@ function ready() { if (!ENVIRONMENT_IS_WASM_WORKER) { #endif run(); -#if USE_PTHREADS || WASM_WORKERS +#if USE_PTHREADS + } +#endif +#if WASM_WORKERS } #endif #else From 90543ec0c0d634a44781b49f9037b565e9b83d4d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jukka=20Jyl=C3=A4nki?= Date: Tue, 22 Feb 2022 12:31:13 +0200 Subject: [PATCH 34/65] Add support for simultaneous pthreads + Wasm workers. --- emcc.py | 2 - src/parseTools.js | 5 ++- src/shell_minimal.js | 5 +++ tests/test_browser.py | 6 +++ tests/wasm_worker/wasm_worker_and_pthread.c | 50 +++++++++++++++++++++ 5 files changed, 64 insertions(+), 4 deletions(-) create mode 100644 tests/wasm_worker/wasm_worker_and_pthread.c diff --git a/emcc.py b/emcc.py index 448dab08a8dc..220ab7b6aa2b 100755 --- a/emcc.py +++ b/emcc.py @@ -2184,8 +2184,6 @@ def include_and_export(name): exit_with_error('TODO: -s MAIN_MODULE=1 is currently not supported with -s WASM_WORKERS!') if settings.PROXY_TO_WORKER: exit_with_error('--proxy-to-worker is not supported with -s WASM_WORKERS!') - if settings.USE_PTHREADS: - exit_with_error('TODO: -pthread and -s USE_PTHREADS=1 are currently not supported with -s WASM_WORKERS!') def check_memory_setting(setting): if settings[setting] % webassembly.WASM_PAGE_SIZE != 0: diff --git a/src/parseTools.js b/src/parseTools.js index 39ff559e3478..28f1546f4b22 100644 --- a/src/parseTools.js +++ b/src/parseTools.js @@ -938,8 +938,9 @@ function modifyFunction(text, func) { } function runOnMainThread(text) { - assert(!WASM_WORKERS || !USE_PTHREADS); // USE_PTHREADS and WASM_WORKERS not supported simultaneously. - if (WASM_WORKERS) { + if (WASM_WORKERS && USE_PTHREADS) { + return 'if (!ENVIRONMENT_IS_WASM_WORKER && !ENVIRONMENT_IS_PTHREAD) { ' + text + ' }'; + } else if (WASM_WORKERS) { return 'if (!ENVIRONMENT_IS_WASM_WORKER) { ' + text + ' }'; } else if (USE_PTHREADS) { return 'if (!ENVIRONMENT_IS_PTHREAD) { ' + text + ' }'; diff --git a/src/shell_minimal.js b/src/shell_minimal.js index da40624edd11..9ca55e2dc379 100644 --- a/src/shell_minimal.js +++ b/src/shell_minimal.js @@ -157,7 +157,12 @@ var _scriptDir = (typeof document != 'undefined' && document.currentScript) ? do // MINIMAL_RUNTIME does not support --proxy-to-worker option, so Worker and Pthread environments // coincide. +#if WASM_WORKERS +var ENVIRONMENT_IS_WORKER = typeof importScripts == 'function', + ENVIRONMENT_IS_PTHREAD = ENVIRONMENT_IS_WORKER && !ENVIRONMENT_IS_WASM_WORKER; +#else var ENVIRONMENT_IS_WORKER = ENVIRONMENT_IS_PTHREAD = typeof importScripts == 'function'; +#endif var currentScriptUrl = typeof _scriptDir != 'undefined' ? _scriptDir : ((typeof document != 'undefined' && document.currentScript) ? document.currentScript.src : undefined); #endif // USE_PTHREADS diff --git a/tests/test_browser.py b/tests/test_browser.py index 2a61f33fc3ae..e867514372ac 100644 --- a/tests/test_browser.py +++ b/tests/test_browser.py @@ -5197,6 +5197,12 @@ def test_wasm_worker_malloc(self): self.skipTest('skipped to debug CI') self.btest(path_from_root('tests', 'wasm_worker', 'malloc_wasm_worker.c'), expected='0', args=['-sWASM_WORKERS']) + # Tests Wasm Worker+pthreads simultaneously + @also_with_minimal_runtime + def test_wasm_worker_and_pthreads(self): + self.skipTest('skipped to debug CI') + self.btest(path_from_root('tests', 'wasm_worker', 'wasm_worker_and_pthread.c'), expected='0', args=['-sWASM_WORKERS', '-pthread']) + # Tests emscripten_wasm_worker_self_id() function @also_with_minimal_runtime def test_wasm_worker_self_id(self): diff --git a/tests/wasm_worker/wasm_worker_and_pthread.c b/tests/wasm_worker/wasm_worker_and_pthread.c new file mode 100644 index 000000000000..212b78a4f10e --- /dev/null +++ b/tests/wasm_worker/wasm_worker_and_pthread.c @@ -0,0 +1,50 @@ +#include +#include +#include +#include +#include + +volatile int pthread_ran = 0; + +EM_JS(int, am_i_pthread, (), { + return ENVIRONMENT_IS_PTHREAD; +}); + +EM_JS(int, am_i_wasm_worker, (), { + return ENVIRONMENT_IS_WASM_WORKER; +}); + +void *thread_main(void *arg) +{ + EM_ASM(out('hello from pthread!')); + assert(am_i_pthread()); + assert(!am_i_wasm_worker()); + assert(!emscripten_current_thread_is_wasm_worker()); + assert(emscripten_wasm_worker_self_id() == 0); + pthread_ran = 1; + return 0; +} + +void worker_main() +{ + EM_ASM(out('hello from wasm worker!')); + assert(!am_i_pthread()); + assert(am_i_wasm_worker()); + assert(emscripten_current_thread_is_wasm_worker()); + assert(emscripten_wasm_worker_self_id() != 0); + + while(!emscripten_atomic_cas_u32((void*)pthread_ran, 0, 1)) + emscripten_wasm_worker_sleep(10); +#ifdef REPORT_RESULT + REPORT_RESULT(0); +#endif +} + +int main() +{ + pthread_t thread; + pthread_create(&thread, NULL, thread_main, NULL); + + emscripten_wasm_worker_t worker = emscripten_malloc_wasm_worker(/*stack size: */1024); + emscripten_wasm_worker_post_function_v(worker, worker_main); +} From ce6040e70d6b42ca3b7ea8cd4e144b29883f2775 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jukka=20Jyl=C3=A4nki?= Date: Tue, 22 Feb 2022 12:59:06 +0200 Subject: [PATCH 35/65] Do not pass redundant TLS size to Wasm Worker creation side. --- src/library_wasm_worker.js | 8 ++------ system/lib/compiler-rt/stack_limits.S | 2 +- system/lib/wasm_worker/library_wasm_worker.c | 6 +++--- 3 files changed, 6 insertions(+), 10 deletions(-) diff --git a/src/library_wasm_worker.js b/src/library_wasm_worker.js index f8420b92bf47..438287b0b8d2 100644 --- a/src/library_wasm_worker.js +++ b/src/library_wasm_worker.js @@ -34,7 +34,7 @@ mergeInto(LibraryManager.library, { // Run the C side Worker initialization for stack and TLS. _emscripten_wasm_worker_initialize(m['sb'], m['sz'] #if !WASM_WORKERS_NO_TLS - , m['tb'], m['tz'] + , m['tb'] #endif ); // The above function initializes the stack for this Worker, but C code cannot @@ -72,13 +72,10 @@ mergeInto(LibraryManager.library, { + '_wasm_workers[0] = this;\n' + 'addEventListener("message", __wasm_worker_appendToQueue);\n' + '}\n', - _emscripten_create_wasm_worker_with_tls: function(stackLowestAddress, stackSize, tlsAddress, tlsSize) { + _emscripten_create_wasm_worker_with_tls: function(stackLowestAddress, stackSize, tlsAddress) { #if ASSERTIONS assert(stackLowestAddress % 16 == 0); assert(stackSize % 16 == 0); -#if !WASM_WORKERS_NO_TLS - assert(tlsAddress != 0 || tlsSize == 0); -#endif #endif let worker = _wasm_workers[_wasm_workers_id] = new Worker( #if WASM_WORKERS == 2 // WASM_WORKERS=2 mode embeds .ww.js file contents into the main .js file as a Blob URL. (convenient, but not CSP security safe, since this is eval-like) @@ -105,7 +102,6 @@ mergeInto(LibraryManager.library, { 'sz': stackSize, // sz = stack size #if !WASM_WORKERS_NO_TLS 'tb': tlsAddress, // tb = TLS base - 'tz': tlsSize // tz = TLS size #endif }); worker.addEventListener('message', __wasm_worker_runPostMessage); diff --git a/system/lib/compiler-rt/stack_limits.S b/system/lib/compiler-rt/stack_limits.S index 28d28ec01b58..929c05e4dd44 100644 --- a/system/lib/compiler-rt/stack_limits.S +++ b/system/lib/compiler-rt/stack_limits.S @@ -82,7 +82,7 @@ emscripten_stack_get_free: # __stack_base and __stack_end globals from a separate file as externs in order for that to work. .globl emscripten_wasm_worker_initialize emscripten_wasm_worker_initialize: - .functype emscripten_wasm_worker_initialize (PTR, i32, PTR, i32) -> () + .functype emscripten_wasm_worker_initialize (PTR, i32, PTR) -> () local.get 0 global.set __stack_end diff --git a/system/lib/wasm_worker/library_wasm_worker.c b/system/lib/wasm_worker/library_wasm_worker.c index f8d6852da993..4803dbc77a5e 100644 --- a/system/lib/wasm_worker/library_wasm_worker.c +++ b/system/lib/wasm_worker/library_wasm_worker.c @@ -14,7 +14,7 @@ // Internal implementation function in JavaScript side that emscripten_create_wasm_worker() calls to // to perform the wasm worker creation. -emscripten_wasm_worker_t _emscripten_create_wasm_worker_with_tls(void *stackLowestAddress, uint32_t stackSize, void *tlsAddress, uint32_t tlsSize); +emscripten_wasm_worker_t _emscripten_create_wasm_worker_with_tls(void *stackLowestAddress, uint32_t stackSize, void *tlsAddress); emscripten_wasm_worker_t _emscripten_create_wasm_worker_no_tls(void *stackLowestAddress, uint32_t stackSize); void __wasm_init_tls(void *memory); @@ -40,7 +40,7 @@ emscripten_wasm_worker_t emscripten_create_wasm_worker_with_tls(void *stackLowes assert(tlsSize != 0 || __builtin_wasm_tls_size() == 0 && "Program code contains TLS: please use function emscripten_create_wasm_worker_with_tls() to create a Wasm Worker!"); assert(tlsSize == __builtin_wasm_tls_size() && "TLS size mismatch! Please reserve exactly __builtin_wasm_tls_size() TLS memory in a call to emscripten_create_wasm_worker_with_tls()"); assert(tlsAddress != 0 || tlsSize == 0); - return _emscripten_create_wasm_worker_with_tls((void*)stackLowestAddress, stackSize, tlsAddress, tlsSize); + return _emscripten_create_wasm_worker_with_tls((void*)stackLowestAddress, stackSize, tlsAddress); #endif } @@ -62,7 +62,7 @@ emscripten_wasm_worker_t emscripten_malloc_wasm_worker(uint32_t stackSize) return emscripten_create_wasm_worker_no_tls(memalign(16, stackSize), stackSize); #else uint32_t tlsSize = __builtin_wasm_tls_size(); - return emscripten_create_wasm_worker_with_tls(memalign(16, stackSize), stackSize, memalign(__builtin_wasm_tls_align(), tlsSize), tlsSize); + return emscripten_create_wasm_worker_with_tls(memalign(16, stackSize), stackSize, tlsSize ? memalign(__builtin_wasm_tls_align(), tlsSize) : 0, tlsSize); #endif } From 81b0543e4cec21238c39fc3658fc5289a602a2f1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jukka=20Jyl=C3=A4nki?= Date: Tue, 22 Feb 2022 13:01:37 +0200 Subject: [PATCH 36/65] Update emcc.py wasm worker deps --- emcc.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/emcc.py b/emcc.py index 220ab7b6aa2b..f93d8d7d2823 100755 --- a/emcc.py +++ b/emcc.py @@ -2096,7 +2096,9 @@ def phase_linker_setup(options, state, newargs, user_settings): if settings.WASM_WORKERS: # TODO: After #15982 is resolved, these dependencies can be declared in library_wasm_worker.js # instead of having to record them here. - settings.EXPORTED_FUNCTIONS += ['_emscripten_stack_set_limits', '_emscripten_wasm_worker_initialize'] + wasm_worker_imports = ['_emscripten_wasm_worker_initialize'] + settings.EXPORTED_FUNCTIONS += wasm_worker_imports + building.user_requested_exports.update(wasm_worker_imports) settings.DEFAULT_LIBRARY_FUNCS_TO_INCLUDE += ['_wasm_worker_initializeRuntime'] # set location of Wasm Worker bootstrap JS file if settings.WASM_WORKERS == 1: From fbc4fdce9a00bf185d11e1725a2c43c23c100f2f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jukka=20Jyl=C3=A4nki?= Date: Wed, 23 Feb 2022 13:50:41 +0200 Subject: [PATCH 37/65] Remove special handling of .S files in system_libs build --- tools/system_libs.py | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/tools/system_libs.py b/tools/system_libs.py index 51e72310726f..a3576ab622fc 100644 --- a/tools/system_libs.py +++ b/tools/system_libs.py @@ -50,8 +50,6 @@ def get_base_cflags(force_object_files=False): flags += ['-sRELOCATABLE'] if settings.MEMORY64: flags += ['-sMEMORY64=' + str(settings.MEMORY64)] - if settings.WASM_WORKERS: - flags += ['-sWASM_WORKERS'] return flags @@ -292,7 +290,6 @@ def build_objects(self, build_dir): commands = [] objects = [] cflags = self.get_cflags() - base_flags = get_base_cflags() case_insensitive = is_case_insensitive(build_dir) for src in self.get_files(): object_basename = shared.unsuffixed_basename(src) @@ -312,13 +309,12 @@ def build_objects(self, build_dir): cmd = [shared.EMCC] else: cmd = [shared.EMXX] + + cmd += cflags if ext in ('.s', '.S'): - cmd += base_flags # TODO(sbc) There is an llvm bug that causes a crash when `-g` is used with # assembly files that define wasm globals. - cmd.remove('-g') - else: - cmd += cflags + cmd = list(filter(lambda arg: arg != '-g', cmd)) cmd = self.customize_build_cmd(cmd, src) commands.append(cmd + ['-c', src, '-o', o]) objects.append(o) From 75d51c858571c863bdc3be0ed3ebb456372b1eba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jukka=20Jyl=C3=A4nki?= Date: Wed, 23 Feb 2022 19:43:42 +0200 Subject: [PATCH 38/65] Update documentation --- .../docs/api_reference/wasm_workers.rst | 236 ++++++++++++++---- system/include/emscripten/wasm_worker.h | 11 +- 2 files changed, 193 insertions(+), 54 deletions(-) diff --git a/site/source/docs/api_reference/wasm_workers.rst b/site/source/docs/api_reference/wasm_workers.rst index 324fb8e4c6a0..cd5f8be3937e 100644 --- a/site/source/docs/api_reference/wasm_workers.rst +++ b/site/source/docs/api_reference/wasm_workers.rst @@ -33,6 +33,10 @@ which shares the same WebAssembly.Module and WebAssembly.Memory object. Then a ``postMessage()`` is passed to the Worker to ask it to execute the function ``run_in_worker()`` to print a string. +For more complex threading scenarios, see the functions ``emscripten_create_wasm_worker_with_tls()`` +and ``emscripten_create_wasm_worker_no_tls()`` on how to manage the memory resources that +the thread will use. + Introduction ============ @@ -59,27 +63,191 @@ different. The focus on Pthreads API is on portability and cross-platform compatibility. This API is best used in scenarios where portability is most important, e.g. when a codebase is -cross-compiled to multiple platforms, like to a native Linux x64 executable and an -Emscripten WebAssembly based program. +cross-compiled to multiple platforms, like building both a native Linux x64 executable and an +Emscripten WebAssembly based web site. Pthreads API in Emscripten seeks to carefully emulate compatibility and the features that the native Pthreads platforms already provide. This helps porting large C/C++ codebases over to WebAssembly. -Wasm Workers API on the other hand seeks to provide a more "direct mapping" to the web -multithreading primitives as they exist on the web. If an application is only developed to -target WebAssembly, and portability is not a concern, then using Wasm Workers can provide -great benefits in the form of simpler compiled output, less complexity, smaller code size -and better performance. +Wasm Workers API on the other hand seeks to provide a "direct mapping" to the web +multithreading primitives as they exist on the web, and call it a day. If an application +is only developed to target WebAssembly, and portability is not a concern, then using Wasm +Workers can provide great benefits in the form of simpler compiled output, less complexity, +smaller code size and possibly better performance. However this benefit might not be an obvious win. The Pthreads API was designed to be useful from the synchronous C/C++ language, whereas Web Workers are designed to be useful from asynchronous JavaScript. WebAssembly C/C++ programs can find themselves somewhere in the middle. -To further understand the differences between Pthreads and Wasm Workers, refer to the following -table. +Pthreads and Wasm Workers share several similarities: + + * Both can use emscripten_atomic_* Atomics API, + * Both can use GCC __sync_* Atomics API, + * Both types of threads have a local stack. + * Both types of threads have thread-local storage (TLS) support via ``thread_local`` (C++11), + ``_Thread_local`` (C11) and ``__thread`` (GNU11) keywords. + * Both types of threads support TLS via explicitly linked in Wasm globals (see + ``tests/wasm_worker/wasm_worker_tls_wasm_assembly.c/.S`` for example code) + * Both types of threads have a concept of a thread ID (``pthread_self()`` for pthreads, + ``emscripten_wasm_worker_self_id()`` for Wasm Workers) + * Both types of threads can perform an event-based and an infinite loop programming model. + * Both can use ``EM_ASM`` and ``EM_JS`` API to execute JS code on the calling thread. + * Both can call out to JS library functions (linked in with ``--js-library`` directive) to + execute JS code on the calling thread. + * Neither pthreads nor Wasm Workers can be used in conjunction with ``-sSINGLE_FILE=1`` linker flag. + +However, the differences are more notable. + +Pthreads can proxy JS functions +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Only pthreads can use the ``MAIN_THREAD_EM_ASM*()`` and ``MAIN_THREAD_ASYNC_EM_ASM()`` functions and +the ``foo__proxy: 'sync'/'async'`` proxying directive in JS libraries. + +Wasm Workers on the other hand do not provide a built-in JS function proxying facility. Proxying a JS +function with Wasm Workers can be done by explicitly passing the address of that function to the +``emscripten_wasm_worker_post_function_*`` API. + +If you need to synchronously wait for the posted function to finish from within a Worker, use one of +the ``emscripten_wasm_worker_*()`` thread synchronization functions to sleep the calling thread until +the callee has finished the operation. + +Note that Wasm Workers cannot + +Pthreads have cancellation points +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +At the expense of performance and code size, pthreads implement a notion of **POSIX cancellation +points** (``pthread_cancel()``, ``pthread_testcancel()``). + +Wasm Workers are more lightweight and performant by not enabling that concept. + +Pthreads may start up synchronously - Wasm Workers always start asynchronously +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Creating new Workers can be slow. Spawning a Worker in JavaScript is an asynchronous operation. In order +to support synchronous pthread startup (for applications that need it) and to improve thread startup +performance, pthreads are hosted in a cached Emscripten runtime managed Worker pool. + +Wasm Workers omit this concept, and as result Wasm Workers will always start up asynchronously. +If you need to detect when a Wasm Worker has started up, post a ping-pong function and reply pair +manually between the Worker and its creator. If you need to spin up new threads quickly, consider +managing a pool of Wasm Workers yourself. + +Pthread topology is flat - Wasm Workers are hierarchical +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +On the web, if a Worker spawns a child Worker of its own, it will create a nested Worker hierarchy +that the main thread cannot directly access. To sidestep portability issues stemming from this kind +of topology, pthreads flatten the Worker creation chain under the hood so that only the main browser thread +ever spawns threads. + +Wasm Workers do not implement this kind of topology flattening, and creating a Wasm Worker in a +Wasm Worker will produce a nested Worker hierarchy. If you need to create Wasm Workers from within +a Wasm Worker, consider which type of hierarchy you would like, and if necessary, flatten the +hierarchy manually by posting the Worker creation over to the main thread yourself. + +Note that support for nested Workers varies across browsers. As of 02/2022, nested Workers are `not +supported in Safari `_. See `here +`_ for a polyfill. + +Pthreads can use the Wasm Worker synchronization API, but not vice versa +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +The multithreading synchronization primitives offered in ``emscripten/wasm_worker.h`` +(``emscripten_lock_*``, ``emscripten_semaphore_*``, ``emscripten_condvar_*``) can be freely invoked +from within pthreads if one so wishes, but Wasm Workers cannot utilize any of the synchronization +functionality in the Pthread API (``pthread_mutex_*``, ``pthread_cond_``, ``pthread_rwlock_*``, etc), +since they lack the needed pthread runtime. + +Pthreads have a "thread main" function and atexit handlers +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +The startup/execution model of pthreads is to start up executing a given thread entry point function. +When that function exits, the pthread will also (by default) quit, and the Worker hosting that pthread +will return to the Worker pool to wait for another thread to be created on it. + +Wasm Workers instead implement the direct web-like model, where a newly created Worker sits idle in its +event loop, waiting for functions to be posted to it. When those functions finish, the Worker will +return to its event loop, waiting to receive more functions (or worker scope web events) to execute. +A Wasm Worker will only quit with a call to ``emscripten_terminate_wasm_worker(worker_id)`` or +``emscripten_terminate_all_wasm_workers()``. + +Pthreads allow one to register thread exit handlers via ``pthread_atexit``, which will be called when +the thread quits. Wasm Workers do not have this concept. + +Pthreads have a per-thread incoming proxy message queue, Wasm Workers do not +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +In order to enable flexible synchronous execution of code on other threads, and to implement support +APIs for example for MEMFS filesystem and Offscreen Framebuffer (WebGL emulated from a Worker) features, +main browser thread and each pthread have a system-backed "proxy message queue" to receive messages. + +This enables user code to call API functions ``emscripten_sync_run_in_main_thread*()``, +``emscripten_sync_run_in_main_runtime_thread()``, ``emscripten_async_run_in_main_runtime_thread()``, +``emscripten_dispatch_to_thread()``, etc. from ``emscripten/threading.h`` to perform proxied calls. +Wasm Workers do not provide this functionality. If needed, such messaging should be implemented manually +by users via regular multithreaded synchronized programming techniques (mutexes, futexes, semaphores, etc.) + +Pthreads synchronize wallclock times +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Another portability aiding emulation feature that Pthreads provide is that the time values returned by +``emscripten_get_now()`` are synchronized to a common time base across all threads. + +Wasm Workers omit this concept, and it is recommended to use the function ``emscripten_performance_now()`` +for high performance timing in a Wasm Worker, and avoid comparing resulting values across Workers, or +manually synchronize them. + +Input events API backproxies only to pthreads +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +The multithreaded input API provided in ``emscripten/html5.h`` only works with the pthread API. When +calling any of the functions ``emscripten_set_*_callback_on_thread()``, one can choose the target +pthread to be the recipient of the received events. + +With Wasm Workers, if desired, "backproxying" events from the main browser thread to a Wasm Worker +should be implemented manually e.g. by using the ``emscripten_wasm_worker_post_function_*()`` API family. + +However note that backproxying input events has a drawback that it prevents security sensitive operations, +like fullscreen requests, pointer locking and audio playback resuming, since handling the input event +is detached from the event callback context executing the initial operation. + +Pthread vs emscripten_lock implementation differences +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +The mutex implementation from ``pthread_mutex_*`` has a few different creation options, one being a +"recursive" mutex. + +The lock implemented by ``emscripten_lock_*`` API is not recursive (and does not provide an option). + +Pthreads also offer a programming guard against a programming error that one thread would not release +a lock that is owned by another thread. ``emscripten_lock_*`` API does not track lock ownership. + +Memory requirements +^^^^^^^^^^^^^^^^^^^ + +Pthreads have a fixed dependency to dynamic memory allocation, and perform calls to ``malloc`` and ``free`` +to allocate thread specific data, stacks and TLS slots. + +With the exception of the helper function ``emscripten_malloc_wasm_worker()``, Wasm Workers are not dependent +on a dynamic memory allocator. Memory allocation needs are met by the caller at Worker creation time, and +can be statically placed if desired. + +Generated code size +^^^^^^^^^^^^^^^^^^^ + +The disk size overhead from pthreads is on the order of a few hundred KBs. Wasm Workers runtime on the other +hand is optimized for tiny deployments, just a few hundred bytes on disk. + +API Differences +^^^^^^^^^^^^^^^ + +To further understand the different APIs available between Pthreads and Wasm Workers, refer to the following +table. .. raw:: html @@ -88,35 +256,11 @@ table. Pthreads Wasm Workers - Workers vs threads - Worker is either dormant or hosts an active thread. - Each Worker is an active thread (colloquially synonymous). - - Worker pooling - Dormant and active Workers reside in a system pool.
Worker returns to pool after its hosted thread terminates. - Not pooled (implement pooling yourself) - - Thread startup - Threads start synchronously and fast if enough dormant Workers available in pool to host, asynchronously and slow otherwise. - Workers always start asynchronously and slow. - - Thread entry point - Thread starts with execution of an entry point function, returning from that function (by default) exits the thread. - No concept of a thread entry point, created Workers are idle after creation, not executing any user code until functions are posted to them. - Thread termination - Thread terminates by returning from entry point, or by calling
pthread_exit(code)
or by main thread calling
pthread_kill(code)
+ Thread calls
pthread_exit(status)
or main thread calls
pthread_kill(code)
Worker cannot terminate itself, parent thread terminates by calling
emscripten_terminate_wasm_worker(worker)
- Dynamic memory (malloc) utilization - Requires dynamic memory allocator, allocates memory for internal operation. - Dynamic memory allocator not necessary, manual placement allocation possible. - - Code size overhead - Few hundred KBs - Few KBs - - Thread stack size + Thread stack Specify in pthread_attr_t structure. Manage thread stack area explicitly with
emscripten_create_wasm_worker_*_tls()
functions, or
automatically allocate stack with
emscripten_malloc_wasm_worker()
API. @@ -130,9 +274,9 @@ table. Creating a pthread obtains its ID. Call
pthread_self()
to acquire ID of calling thread. Creating a Worker obtains its ID. Call
emscripten_wasm_worker_self_id()
acquire ID of calling thread. - emscripten_get_now() - All pthreads are synchronized to the same wallclock time base, so emscripten_get_now() return values across threads are comparable. - Workers each have their own wallclock time base, emscripten_get_now() is not synchronized across them. + High resolution timer + ``emscripten_get_now()`` + ``emscripten_performance_now()`` Synchronous blocking on main thread Synchronization primitives internally fall back to busy spin loops. @@ -150,14 +294,6 @@ table. emscripten/threading.h API for proxying function calls to other threads. Use emscripten_wasm_worker_post_function_*() API to message functions to other threads. These messages follow event queue semantics rather than proxy queue semantics. - JS Library Main Thread Proxying - Use the foo__proxy: 'sync'/'async' directive to specify a JS function to be run on the main thread context. - N/A. JS code is always run on the calling thread context. Functions with __proxy directive will abort at runtime if called in a Worker. - - Proxied EM_ASM - Use MAIN_THREAD_EM_ASM() and MAIN_THREAD_ASYNC_EM_ASM() to proxy EM_ASM code blocks to the main thread. - N/A. Only calling thread EM_ASM() function blocks are possible. - Build flags Compile and link with -pthread Compile and link with -sWASM_WORKERS=1 @@ -203,12 +339,10 @@ table. -Limitations and TODOs -===================== - -Currently it is not possible to simultaneously use pthreads and Wasm Workers in the same application, but this may change in the future. +Limitations +=========== -Also, the following build options are not supported at the moment with Wasm Workers: +The following build options are not supported at the moment with Wasm Workers: - -sSINGLE_FILE=1 - Dynamic linking (-sLINKABLE=1, -sMAIN_MODULE=1, -sSIDE_MODULE=1) diff --git a/system/include/emscripten/wasm_worker.h b/system/include/emscripten/wasm_worker.h index a06e03630626..5fc52afb5e33 100644 --- a/system/include/emscripten/wasm_worker.h +++ b/system/include/emscripten/wasm_worker.h @@ -40,9 +40,14 @@ emscripten_wasm_worker_t emscripten_malloc_wasm_worker(uint32_t stackSize); emscripten_wasm_worker_t emscripten_create_wasm_worker_no_tls(void *stackLowestAddress, uint32_t stackSize); emscripten_wasm_worker_t emscripten_create_wasm_worker_with_tls(void *stackLowestAddress, uint32_t stackSize, void *tlsAddress, uint32_t tlsSize); +// Terminates the given Wasm Worker some time after it has finished executing its current, or possibly some subsequent +// posted functions. Note that this function is not C++ RAII safe, but you must manually coordinate to release any +// resources from the given Worker that it may have allocated from the heap or may have stored on its TLS slots. +// There are no TLS destructors that would execute. // Exists, but is a no-op if not building with Wasm Workers enabled (-s WASM_WORKERS=0) void emscripten_terminate_wasm_worker(emscripten_wasm_worker_t id); +// Note the comment on emscripten_terminate_wasm_worker(id) about thread destruction. // Exists, but is a no-op if not building with Wasm Workers enabled (-s WASM_WORKERS=0) void emscripten_terminate_all_wasm_workers(void); @@ -57,9 +62,9 @@ uint32_t emscripten_wasm_worker_self_id(void); /* emscripten_wasm_worker_post_function_*: Post a pointer to a C/C++ function to be executed on the target Wasm Worker (via sending a postMessage() to the target thread). Notes: - - The target worker 'id' must have been created by the same thread that is calling this function. That is, - a Wasm Worker cannot send a message to another Worker via this function. - - If running in a Wasm Worker context, specify worker ID 0 to pass a message to the parent thread. + - If running inside a Wasm Worker, specify worker ID 0 to pass a message to the parent thread. + - When specifying non-zero ID, the target worker must have been created by the calling thread. That is, + a Wasm Worker can only send a message to its parent or its children, but not to its siblings. - The target function pointer will be executed on the target Worker only after it yields back to its event loop. If the target Wasm Worker executes an infinite loop that never yields, then the function pointer will never be called. From 553dd6b916797c01f32ef24017a0e61e04cab9bd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jukka=20Jyl=C3=A4nki?= Date: Wed, 23 Feb 2022 19:58:06 +0200 Subject: [PATCH 39/65] Add code size test. --- tests/code_size/hello_wasm_worker_wasm.js | 178 ++++++++++++++++++++ tests/code_size/hello_wasm_worker_wasm.json | 10 ++ tests/test_other.py | 10 +- tests/wasm_worker/wasm_worker_code_size.c | 14 ++ 4 files changed, 209 insertions(+), 3 deletions(-) create mode 100644 tests/code_size/hello_wasm_worker_wasm.js create mode 100644 tests/code_size/hello_wasm_worker_wasm.json create mode 100644 tests/wasm_worker/wasm_worker_code_size.c diff --git a/tests/code_size/hello_wasm_worker_wasm.js b/tests/code_size/hello_wasm_worker_wasm.js new file mode 100644 index 000000000000..bbb813eed446 --- /dev/null +++ b/tests/code_size/hello_wasm_worker_wasm.js @@ -0,0 +1,178 @@ +var Module = Module; + +var ENVIRONMENT_IS_WASM_WORKER = Module["$ww"]; + +function ready() { + if (!ENVIRONMENT_IS_WASM_WORKER) { + run(); + } +} + +function abort(what) { + throw what; +} + +function TextDecoderWrapper(encoding) { + var textDecoder = new TextDecoder(encoding); + this.decode = data => { + if (data.buffer instanceof SharedArrayBuffer) { + data = new Uint8Array(data); + } + return textDecoder.decode.call(textDecoder, data); + }; +} + +var UTF8Decoder = new TextDecoderWrapper("utf8"); + +function UTF8ToString(ptr, maxBytesToRead) { + if (!ptr) return ""; + var maxPtr = ptr + maxBytesToRead; + for (var end = ptr; !(end >= maxPtr) && HEAPU8[end]; ) ++end; + return UTF8Decoder.decode(HEAPU8.subarray(ptr, end)); +} + +var HEAP8, HEAP16, HEAP32, HEAPU8, HEAPU16, HEAPU32, HEAPF32, HEAPF64, wasmMemory, buffer, wasmTable; + +function updateGlobalBufferAndViews(b) { + buffer = b; + HEAP8 = new Int8Array(b); + HEAP16 = new Int16Array(b); + HEAP32 = new Int32Array(b); + HEAPU8 = new Uint8Array(b); + HEAPU16 = new Uint16Array(b); + HEAPU32 = new Uint32Array(b); + HEAPF32 = new Float32Array(b); + HEAPF64 = new Float64Array(b); +} + +wasmMemory = Module["mem"] || new WebAssembly.Memory({ + initial: 256, + maximum: 256, + shared: true +}); + +updateGlobalBufferAndViews(wasmMemory.buffer); + +var ASM_CONSTS = { + 1916: function() { + console.log("Hello from wasm worker!"); + } +}; + +function getWasmTableEntry(funcPtr) { + return wasmTable.get(funcPtr); +} + +function ___assert_fail(condition, filename, line, func) { + abort("Assertion failed: " + UTF8ToString(condition) + ", at: " + [ filename ? UTF8ToString(filename) : "unknown filename", line, func ? UTF8ToString(func) : "unknown function" ]); +} + +var _wasm_workers = {}; + +var _wasm_workers_id = 1; + +function __wasm_worker_appendToQueue(e) { + __wasm_worker_delayedMessageQueue.push(e); +} + +function __wasm_worker_runPostMessage(e) { + let data = e.data, wasmCall = data["_wsc"]; + wasmCall && getWasmTableEntry(wasmCall)(...data["x"]); +} + +function __emscripten_create_wasm_worker_with_tls(stackLowestAddress, stackSize, tlsAddress) { + let worker = _wasm_workers[_wasm_workers_id] = new Worker(Module["$wb"]); + worker.postMessage({ + $ww: _wasm_workers_id, + wasm: Module["wasm"], + js: Module["js"], + mem: wasmMemory, + sb: stackLowestAddress, + sz: stackSize, + tb: tlsAddress + }); + worker.addEventListener("message", __wasm_worker_runPostMessage); + return _wasm_workers_id++; +} + +var __wasm_worker_delayedMessageQueue = []; + +function __wasm_worker_initializeRuntime() { + let m = Module; + _emscripten_wasm_worker_initialize(m["sb"], m["sz"], m["tb"]); + removeEventListener("message", __wasm_worker_appendToQueue); + __wasm_worker_delayedMessageQueue.forEach(__wasm_worker_runPostMessage); + __wasm_worker_delayedMessageQueue = null; + addEventListener("message", __wasm_worker_runPostMessage); +} + +var readAsmConstArgsArray = []; + +function readAsmConstArgs(sigPtr, buf) { + readAsmConstArgsArray.length = 0; + var ch; + buf >>= 2; + while (ch = HEAPU8[sigPtr++]) { + var readAsmConstArgsDouble = ch < 105; + if (readAsmConstArgsDouble && buf & 1) buf++; + readAsmConstArgsArray.push(readAsmConstArgsDouble ? HEAPF64[buf++ >> 1] : HEAP32[buf]); + ++buf; + } + return readAsmConstArgsArray; +} + +function _emscripten_asm_const_int(code, sigPtr, argbuf) { + var args = readAsmConstArgs(sigPtr, argbuf); + return ASM_CONSTS[code].apply(null, args); +} + +function _emscripten_resize_heap(requestedSize) { + var oldSize = HEAPU8.length; + requestedSize = requestedSize >>> 0; + return false; +} + +function _emscripten_wasm_worker_post_function_v(id, funcPtr) { + _wasm_workers[id].postMessage({ + _wsc: funcPtr, + x: [] + }); +} + +if (ENVIRONMENT_IS_WASM_WORKER) { + _wasm_workers[0] = this; + addEventListener("message", __wasm_worker_appendToQueue); +} + +var asmLibraryArg = { + b: ___assert_fail, + c: __emscripten_create_wasm_worker_with_tls, + f: _emscripten_asm_const_int, + d: _emscripten_resize_heap, + e: _emscripten_wasm_worker_post_function_v, + a: wasmMemory +}; + +function run() { + var ret = _main(); +} + +function initRuntime(asm) { + if (ENVIRONMENT_IS_WASM_WORKER) return __wasm_worker_initializeRuntime(); + asm["g"](); +} + +var imports = { + a: asmLibraryArg +}; + +var _main, _emscripten_wasm_worker_initialize; + +WebAssembly.instantiate(Module["wasm"], imports).then((function(output) { + var asm = output.instance.exports; + _main = asm["h"]; + _emscripten_wasm_worker_initialize = asm["j"]; + wasmTable = asm["i"]; + initRuntime(asm); + ready(); +})); \ No newline at end of file diff --git a/tests/code_size/hello_wasm_worker_wasm.json b/tests/code_size/hello_wasm_worker_wasm.json new file mode 100644 index 000000000000..48ab60233c71 --- /dev/null +++ b/tests/code_size/hello_wasm_worker_wasm.json @@ -0,0 +1,10 @@ +{ + "a.html": 737, + "a.html.gz": 433, + "a.js": 3937, + "a.js.gz": 1502, + "a.wasm": 2830, + "a.wasm.gz": 1472, + "total": 7504, + "total_gz": 3407 +} diff --git a/tests/test_other.py b/tests/test_other.py index 43f8ca31a411..2cd54413b2f4 100644 --- a/tests/test_other.py +++ b/tests/test_other.py @@ -9099,7 +9099,7 @@ def test(args, closure, opt): # Windows and Mac autorollers, despite the bot being correctly configured to # skip this test in all three platforms (Linux, Mac, and Windows). # The no_windows/no_mac decorators also solve that problem. - @no_windows("Code size is slightly different on Windows") +# @no_windows("Code size is slightly different on Windows") @no_mac("Code size is slightly different on Mac") @parameterized({ 'hello_world_wasm': ('hello_world', False, True), @@ -9111,6 +9111,7 @@ def test(args, closure, opt): 'hello_webgl2_wasm': ('hello_webgl2', False), 'hello_webgl2_wasm2js': ('hello_webgl2', True), 'math': ('math', False), + 'hello_wasm_worker': ('hello_wasm_worker', False, True), }) def test_minimal_runtime_code_size(self, test_name, js, compare_js_output=False): smallest_code_size_args = ['-sMINIMAL_RUNTIME=2', @@ -9133,7 +9134,7 @@ def test_minimal_runtime_code_size(self, test_name, js, compare_js_output=False) '-sNO_FILESYSTEM', '--output_eol', 'linux', '-Oz', - '--closure=1', + '--closure=0', '-DNDEBUG', '-ffast-math'] @@ -9151,13 +9152,15 @@ def test_minimal_runtime_code_size(self, test_name, js, compare_js_output=False) '-sUSES_DYNAMIC_ALLOC', '-lwebgl.js', '-sMODULARIZE'] hello_webgl2_sources = hello_webgl_sources + ['-sMAX_WEBGL_VERSION=2'] + hello_wasm_worker_sources = [test_file('wasm_worker/wasm_worker_code_size.c'), '-sWASM_WORKERS', '-sENVIRONMENT=web,worker'] sources = { 'hello_world': hello_world_sources, 'random_printf': random_printf_sources, 'hello_webgl': hello_webgl_sources, 'math': math_sources, - 'hello_webgl2': hello_webgl2_sources}[test_name] + 'hello_webgl2': hello_webgl2_sources, + 'hello_wasm_worker': hello_wasm_worker_sources}[test_name] def print_percent(actual, expected): if actual == expected: @@ -9286,6 +9289,7 @@ def get_file_gzipped_size(f): print('If this is expected, rerun the test with --rebaseline to update the expected sizes') self.assertEqual(total_output_size, total_expected_size) + # Tests the library_c_preprocessor.js functionality. def test_c_preprocessor(self): self.run_process([EMXX, test_file('test_c_preprocessor.c'), '--js-library', path_from_root('src/library_c_preprocessor.js'), '-sDEFAULT_LIBRARY_FUNCS_TO_INCLUDE=$remove_cpp_comments_in_shaders,$preprocess_c_code']) diff --git a/tests/wasm_worker/wasm_worker_code_size.c b/tests/wasm_worker/wasm_worker_code_size.c new file mode 100644 index 000000000000..2b41122d83b4 --- /dev/null +++ b/tests/wasm_worker/wasm_worker_code_size.c @@ -0,0 +1,14 @@ +#include +#include + +// This file contains an absolute minimal sized Wasm Worker example, to keep a check on generated code size. + +void run_in_worker() +{ + EM_ASM(console.log("Hello from wasm worker!")); +} + +int main() +{ + emscripten_wasm_worker_post_function_v(emscripten_malloc_wasm_worker(1024), run_in_worker); +} From 7f35de8f72d5bd4bdd712cadf475fafc6e578f97 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jukka=20Jyl=C3=A4nki?= Date: Wed, 23 Feb 2022 19:59:38 +0200 Subject: [PATCH 40/65] flake --- tests/test_other.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/test_other.py b/tests/test_other.py index 2cd54413b2f4..15b5960a6d1d 100644 --- a/tests/test_other.py +++ b/tests/test_other.py @@ -9289,7 +9289,6 @@ def get_file_gzipped_size(f): print('If this is expected, rerun the test with --rebaseline to update the expected sizes') self.assertEqual(total_output_size, total_expected_size) - # Tests the library_c_preprocessor.js functionality. def test_c_preprocessor(self): self.run_process([EMXX, test_file('test_c_preprocessor.c'), '--js-library', path_from_root('src/library_c_preprocessor.js'), '-sDEFAULT_LIBRARY_FUNCS_TO_INCLUDE=$remove_cpp_comments_in_shaders,$preprocess_c_code']) From b0f04f0819bfe736a93e4a1c244ad9dc01a61f6b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jukka=20Jyl=C3=A4nki?= Date: Wed, 23 Feb 2022 21:47:50 +0200 Subject: [PATCH 41/65] Update tests and wasm worker MT build --- tests/test_browser.py | 31 --------------------- tests/wasm_worker/wasm_worker_and_pthread.c | 2 +- tools/system_libs.py | 4 +-- 3 files changed, 3 insertions(+), 34 deletions(-) diff --git a/tests/test_browser.py b/tests/test_browser.py index e867514372ac..37ccdff13436 100644 --- a/tests/test_browser.py +++ b/tests/test_browser.py @@ -5171,7 +5171,6 @@ def test_system(self): # Tests the hello_wasm_worker.c documentation example code. @also_with_minimal_runtime def test_wasm_worker_hello(self): - self.skipTest('skipped to debug CI') self.btest(path_from_root('tests', 'wasm_worker', 'hello_wasm_worker.c'), expected='0', args=['-sWASM_WORKERS']) # Tests the WASM_WORKERS=2 build mode, which embeds the Wasm Worker bootstrap JS script file to the main JS file. @@ -5182,44 +5181,37 @@ def test_wasm_worker_embedded(self): # Tests Wasm Worker thread stack setup @also_with_minimal_runtime def test_wasm_worker_thread_stack(self): - self.skipTest('skipped to debug CI') self.btest(path_from_root('tests', 'wasm_worker', 'thread_stack.c'), expected='0', args=['-sWASM_WORKERS']) # Tests Wasm Worker thread stack setup without TLS support active @also_with_minimal_runtime def test_wasm_worker_thread_stack_no_tls(self): - self.skipTest('skipped to debug CI') self.btest(path_from_root('tests', 'wasm_worker', 'thread_stack.c'), expected='0', args=['-sWASM_WORKERS', '-sWASM_WORKERS_NO_TLS']) # Tests emscripten_malloc_wasm_worker() and emscripten_current_thread_is_wasm_worker() functions @also_with_minimal_runtime def test_wasm_worker_malloc(self): - self.skipTest('skipped to debug CI') self.btest(path_from_root('tests', 'wasm_worker', 'malloc_wasm_worker.c'), expected='0', args=['-sWASM_WORKERS']) # Tests Wasm Worker+pthreads simultaneously @also_with_minimal_runtime def test_wasm_worker_and_pthreads(self): - self.skipTest('skipped to debug CI') self.btest(path_from_root('tests', 'wasm_worker', 'wasm_worker_and_pthread.c'), expected='0', args=['-sWASM_WORKERS', '-pthread']) # Tests emscripten_wasm_worker_self_id() function @also_with_minimal_runtime def test_wasm_worker_self_id(self): - self.skipTest('skipped to debug CI') self.btest(path_from_root('tests', 'wasm_worker', 'wasm_worker_self_id.c'), expected='0', args=['-sWASM_WORKERS']) # Tests direct Wasm Assembly .S file based TLS variables in Wasm Workers @also_with_minimal_runtime def test_wasm_worker_tls_wasm_assembly(self): - self.skipTest('skipped to debug CI') self.btest(path_from_root('tests', 'wasm_worker', 'wasm_worker_tls_wasm_assembly.c'), expected='42', args=['-sWASM_WORKERS', path_from_root('tests', 'wasm_worker', 'wasm_worker_tls_wasm_assembly.S')]) # Tests C++11 keyword thread_local for TLS in Wasm Workers @also_with_minimal_runtime def test_wasm_worker_cpp11_thread_local(self): - self.skipTest('skipped to debug CI') self.skipTest('''wasm-ld: /b/s/w/ir/cache/builder/emscripten-releases/llvm-project/llvm/include/llvm/ADT/Optional.h:199: const T &llvm::optional_detail::OptionalStorage::getValue() const & [T = unsigned int]: Assertion `hasVal' failed. PLEASE submit a bug report to https://github.com/llvm/llvm-project/issues/ and include the crash backtrace. Stack dump: @@ -5235,7 +5227,6 @@ def test_wasm_worker_cpp11_thread_local(self): # Tests C11 keyword _Thread_local for TLS in Wasm Workers @also_with_minimal_runtime def test_wasm_worker_c11__Thread_local(self): - self.skipTest('skipped to debug CI') self.skipTest('''wasm-ld: /b/s/w/ir/cache/builder/emscripten-releases/llvm-project/llvm/include/llvm/ADT/Optional.h:199: const T &llvm::optional_detail::OptionalStorage::getValue() const & [T = unsigned int]: Assertion `hasVal' failed. PLEASE submit a bug report to https://github.com/llvm/llvm-project/issues/ and include the crash backtrace. Stack dump: @@ -5252,7 +5243,6 @@ def test_wasm_worker_c11__Thread_local(self): # Tests GCC specific extension keyword __thread for TLS in Wasm Workers @also_with_minimal_runtime def test_wasm_worker_gcc___thread(self): - self.skipTest('skipped to debug CI') self.skipTest('''wasm-ld: /b/s/w/ir/cache/builder/emscripten-releases/llvm-project/llvm/include/llvm/ADT/Optional.h:199: const T &llvm::optional_detail::OptionalStorage::getValue() const & [T = unsigned int]: Assertion `hasVal' failed. PLEASE submit a bug report to https://github.com/llvm/llvm-project/issues/ and include the crash backtrace. Stack dump: @@ -5268,129 +5258,108 @@ def test_wasm_worker_gcc___thread(self): # Tests emscripten_wasm_worker_sleep() @also_with_minimal_runtime def test_wasm_worker_sleep(self): - self.skipTest('skipped to debug CI') self.btest(path_from_root('tests', 'wasm_worker', 'wasm_worker_sleep.c'), expected='1', args=['-sWASM_WORKERS']) # Tests emscripten_terminate_wasm_worker() @also_with_minimal_runtime def test_wasm_worker_terminate(self): - self.skipTest('skipped to debug CI') self.btest(path_from_root('tests', 'wasm_worker', 'terminate_wasm_worker.c'), expected='0', args=['-sWASM_WORKERS']) # Tests emscripten_terminate_all_wasm_workers() @also_with_minimal_runtime def test_wasm_worker_terminate_all(self): - self.skipTest('skipped to debug CI') self.btest(path_from_root('tests', 'wasm_worker', 'terminate_all_wasm_workers.c'), expected='0', args=['-sWASM_WORKERS']) # Tests emscripten_wasm_worker_post_function_*() API @also_with_minimal_runtime def test_wasm_worker_post_function(self): - self.skipTest('skipped to debug CI') self.btest(path_from_root('tests', 'wasm_worker', 'post_function.c'), expected='8', args=['-sWASM_WORKERS']) # Tests emscripten_wasm_worker_post_function_*() API and EMSCRIPTEN_WASM_WORKER_ID_PARENT # to send a message back from Worker to its parent thread. @also_with_minimal_runtime def test_wasm_worker_post_function_to_main_thread(self): - self.skipTest('skipped to debug CI') self.btest(path_from_root('tests', 'wasm_worker', 'post_function_to_main_thread.c'), expected='10', args=['-sWASM_WORKERS']) # Tests emscripten_navigator_hardware_concurrency() and emscripten_atomics_is_lock_free() @also_with_minimal_runtime def test_wasm_worker_hardware_concurrency_is_lock_free(self): - self.skipTest('skipped to debug CI') self.btest(path_from_root('tests', 'wasm_worker', 'hardware_concurrency_is_lock_free.c'), expected='0', args=['-sWASM_WORKERS']) # Tests emscripten_wasm_wait_i32() and emscripten_wasm_notify() functions. @also_with_minimal_runtime def test_wasm_worker_wait32_notify(self): - self.skipTest('skipped to debug CI') self.btest(path_from_root('tests', 'wasm_worker', 'wait32_notify.c'), expected='2', args=['-sWASM_WORKERS']) # Tests emscripten_wasm_wait_i64() and emscripten_wasm_notify() functions. @also_with_minimal_runtime def test_wasm_worker_wait64_notify(self): - self.skipTest('skipped to debug CI') self.btest(path_from_root('tests', 'wasm_worker', 'wait64_notify.c'), expected='2', args=['-sWASM_WORKERS']) # Tests emscripten_atomic_wait_async() function. @also_with_minimal_runtime def test_wasm_worker_wait_async(self): - self.skipTest('skipped to debug CI') self.btest(path_from_root('tests', 'wasm_worker', 'wait_async.c'), expected='0', args=['-sWASM_WORKERS']) # Tests emscripten_atomic_cancel_wait_async() function. @also_with_minimal_runtime def test_wasm_worker_cancel_wait_async(self): - self.skipTest('skipped to debug CI') self.btest(path_from_root('tests', 'wasm_worker', 'cancel_wait_async.c'), expected='1', args=['-sWASM_WORKERS']) # Tests emscripten_atomic_cancel_all_wait_asyncs() function. @also_with_minimal_runtime def test_wasm_worker_cancel_all_wait_asyncs(self): - self.skipTest('skipped to debug CI') self.btest(path_from_root('tests', 'wasm_worker', 'cancel_all_wait_asyncs.c'), expected='1', args=['-sWASM_WORKERS']) # Tests emscripten_atomic_cancel_all_wait_asyncs_at_address() function. @also_with_minimal_runtime def test_wasm_worker_cancel_all_wait_asyncs_at_address(self): - self.skipTest('skipped to debug CI') self.btest(path_from_root('tests', 'wasm_worker', 'cancel_all_wait_asyncs_at_address.c'), expected='1', args=['-sWASM_WORKERS']) # Tests emscripten_lock_init(), emscripten_lock_waitinf_acquire() and emscripten_lock_release() @also_with_minimal_runtime def test_wasm_worker_lock_waitinf(self): - self.skipTest('skipped to debug CI') self.btest(path_from_root('tests', 'wasm_worker', 'lock_waitinf_acquire.c'), expected='4000', args=['-sWASM_WORKERS']) # Tests emscripten_lock_wait_acquire() and emscripten_lock_try_acquire() in Worker. @also_with_minimal_runtime def test_wasm_worker_lock_wait(self): - self.skipTest('skipped to debug CI') self.btest(path_from_root('tests', 'wasm_worker', 'lock_wait_acquire.c'), expected='0', args=['-sWASM_WORKERS']) # Tests emscripten_lock_wait_acquire() between two Wasm Workers. @also_with_minimal_runtime def test_wasm_worker_lock_wait2(self): - self.skipTest('skipped to debug CI') self.btest(path_from_root('tests', 'wasm_worker', 'lock_wait_acquire2.c'), expected='0', args=['-sWASM_WORKERS']) # Tests emscripten_lock_async_acquire() function. @also_with_minimal_runtime def test_wasm_worker_lock_async_acquire(self): - self.skipTest('skipped to debug CI') self.btest(path_from_root('tests', 'wasm_worker', 'lock_async_acquire.c'), expected='0', args=['-sWASM_WORKERS']) # Tests emscripten_lock_busyspin_wait_acquire() in Worker and main thread. @also_with_minimal_runtime def test_wasm_worker_lock_busyspin_wait(self): - self.skipTest('skipped to debug CI') self.btest(path_from_root('tests', 'wasm_worker', 'lock_busyspin_wait_acquire.c'), expected='0', args=['-sWASM_WORKERS']) # Tests emscripten_lock_busyspin_waitinf_acquire() in Worker and main thread. @also_with_minimal_runtime def test_wasm_worker_lock_busyspin_waitinf(self): - self.skipTest('skipped to debug CI') self.btest(path_from_root('tests', 'wasm_worker', 'lock_busyspin_waitinf_acquire.c'), expected='1', args=['-sWASM_WORKERS']) # Tests that proxied JS functions cannot be called from Wasm Workers @also_with_minimal_runtime def test_wasm_worker_no_proxied_js_functions(self): - self.skipTest('skipped to debug CI') self.btest(path_from_root('tests', 'wasm_worker', 'no_proxied_js_functions.c'), expected='0', args=['--js-library', path_from_root('tests', 'wasm_worker', 'no_proxied_js_functions.js'), '-sWASM_WORKERS', '-sASSERTIONS']) # Tests emscripten_semaphore_init(), emscripten_semaphore_waitinf_acquire() and emscripten_semaphore_release() @also_with_minimal_runtime def test_wasm_worker_semaphore_waitinf_acquire(self): - self.skipTest('skipped to debug CI') self.btest(path_from_root('tests', 'wasm_worker', 'semaphore_waitinf_acquire.c'), expected='0', args=['-sWASM_WORKERS']) # Tests emscripten_semaphore_try_acquire() on the main thread @also_with_minimal_runtime def test_wasm_worker_semaphore_try_acquire(self): - self.skipTest('skipped to debug CI') self.btest(path_from_root('tests', 'wasm_worker', 'semaphore_try_acquire.c'), expected='0', args=['-sWASM_WORKERS']) @no_firefox('no 4GB support yet') diff --git a/tests/wasm_worker/wasm_worker_and_pthread.c b/tests/wasm_worker/wasm_worker_and_pthread.c index 212b78a4f10e..b6dedb0f9e3d 100644 --- a/tests/wasm_worker/wasm_worker_and_pthread.c +++ b/tests/wasm_worker/wasm_worker_and_pthread.c @@ -33,7 +33,7 @@ void worker_main() assert(emscripten_current_thread_is_wasm_worker()); assert(emscripten_wasm_worker_self_id() != 0); - while(!emscripten_atomic_cas_u32((void*)pthread_ran, 0, 1)) + while(!emscripten_atomic_cas_u32((void*)&pthread_ran, 0, 1)) emscripten_wasm_worker_sleep(10); #ifdef REPORT_RESULT REPORT_RESULT(0); diff --git a/tools/system_libs.py b/tools/system_libs.py index a3576ab622fc..ddf484b116c3 100644 --- a/tools/system_libs.py +++ b/tools/system_libs.py @@ -498,7 +498,7 @@ def get_default_variation(cls, **kwargs): @classmethod def variations(cls): combos = super(MTLibrary, cls).variations() - # pthreads and Wasm workers are currently not supported together. + # To save on # of variations, pthreads and Wasm workers when used together, just use pthreads variation. return [combo for combo in combos if not combo['is_mt'] or not combo['is_ww']] @@ -1070,7 +1070,7 @@ def get_default_variation(cls, **kwargs): def get_files(self): return files_in_path( path='system/lib/wasm_worker', - filenames=['library_wasm_worker.c' if self.is_ww else 'library_wasm_worker_stub.c']) + filenames=['library_wasm_worker.c' if self.is_ww or self.is_mt else 'library_wasm_worker_stub.c']) class libsockets(MuslInternalLibrary, MTLibrary): From ba81dd284002c34e0ccfddfab82aaad068fee726 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jukka=20Jyl=C3=A4nki?= Date: Wed, 23 Feb 2022 23:35:45 +0200 Subject: [PATCH 42/65] Fix mt build --- tools/system_libs.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tools/system_libs.py b/tools/system_libs.py index ddf484b116c3..13023874fcc2 100644 --- a/tools/system_libs.py +++ b/tools/system_libs.py @@ -1041,7 +1041,7 @@ def get_cflags(self): '-D_DEBUG' if self.debug else '-Oz', '-DSTACK_OVERFLOW_CHECK=' + ('2' if self.stack_check else '0'), '-DWASM_WORKER_NO_TLS=' + ('0' if self.tls else '1')] - if self.is_ww: + if self.is_ww or self.is_mt: cflags += ['-sWASM_WORKERS'] if settings.MAIN_MODULE: cflags += ['-fPIC'] @@ -1049,7 +1049,7 @@ def get_cflags(self): def get_base_name(self): name = 'libwasm_workers' - if not self.is_ww: + if not self.is_ww and not self.is_mt: name += '_stub' if not self.tls: name += '-notls' From 59d11adb5a7669106963ef7f1dcf953e8b575cff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jukka=20Jyl=C3=A4nki?= Date: Thu, 24 Feb 2022 01:49:16 +0200 Subject: [PATCH 43/65] Adjust mt build --- tools/system_libs.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tools/system_libs.py b/tools/system_libs.py index 13023874fcc2..5b630a1908e3 100644 --- a/tools/system_libs.py +++ b/tools/system_libs.py @@ -468,13 +468,13 @@ def get_usable_variations(cls): class MTLibrary(Library): def __init__(self, **kwargs): self.is_mt = kwargs.pop('is_mt') - self.is_ww = kwargs.pop('is_ww') + self.is_ww = kwargs.pop('is_ww') and not self.is_mt super().__init__(**kwargs) def get_cflags(self): cflags = super().get_cflags() if self.is_mt: - cflags += ['-sUSE_PTHREADS'] + cflags += ['-sUSE_PTHREADS', '-sWASM_WORKERS'] if self.is_ww: cflags += ['-sWASM_WORKERS'] return cflags @@ -493,7 +493,7 @@ def vary_on(cls): @classmethod def get_default_variation(cls, **kwargs): - return super().get_default_variation(is_mt=settings.USE_PTHREADS, is_ww=settings.WASM_WORKERS, **kwargs) + return super().get_default_variation(is_mt=settings.USE_PTHREADS, is_ww=settings.WASM_WORKERS and not settings.USE_PTHREADS, **kwargs) @classmethod def variations(cls): From 05d0dc92dcd45eb75bcb07cef7bf92ce46fe26cf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jukka=20Jyl=C3=A4nki?= Date: Thu, 24 Feb 2022 01:58:08 +0200 Subject: [PATCH 44/65] Update code size test --- tests/code_size/hello_wasm_worker_wasm.js | 246 +++++++------------- tests/code_size/hello_wasm_worker_wasm.json | 12 +- tests/test_other.py | 4 +- 3 files changed, 93 insertions(+), 169 deletions(-) diff --git a/tests/code_size/hello_wasm_worker_wasm.js b/tests/code_size/hello_wasm_worker_wasm.js index bbb813eed446..d87640ab118b 100644 --- a/tests/code_size/hello_wasm_worker_wasm.js +++ b/tests/code_size/hello_wasm_worker_wasm.js @@ -1,178 +1,102 @@ -var Module = Module; +var e = Module; -var ENVIRONMENT_IS_WASM_WORKER = Module["$ww"]; - -function ready() { - if (!ENVIRONMENT_IS_WASM_WORKER) { - run(); - } -} - -function abort(what) { - throw what; -} - -function TextDecoderWrapper(encoding) { - var textDecoder = new TextDecoder(encoding); - this.decode = data => { - if (data.buffer instanceof SharedArrayBuffer) { - data = new Uint8Array(data); - } - return textDecoder.decode.call(textDecoder, data); +var f = e.$ww, g = new function(a) { + var c = new TextDecoder(a); + this.l = b => { + b.buffer instanceof SharedArrayBuffer && (b = new Uint8Array(b)); + return c.decode.call(c, b); }; -} - -var UTF8Decoder = new TextDecoderWrapper("utf8"); +}("utf8"); -function UTF8ToString(ptr, maxBytesToRead) { - if (!ptr) return ""; - var maxPtr = ptr + maxBytesToRead; - for (var end = ptr; !(end >= maxPtr) && HEAPU8[end]; ) ++end; - return UTF8Decoder.decode(HEAPU8.subarray(ptr, end)); +function h(a) { + if (!a) return ""; + for (var c = a + NaN, b = a; !(b >= c) && k[b]; ) ++b; + return g.l(k.subarray(a, b)); } -var HEAP8, HEAP16, HEAP32, HEAPU8, HEAPU16, HEAPU32, HEAPF32, HEAPF64, wasmMemory, buffer, wasmTable; - -function updateGlobalBufferAndViews(b) { - buffer = b; - HEAP8 = new Int8Array(b); - HEAP16 = new Int16Array(b); - HEAP32 = new Int32Array(b); - HEAPU8 = new Uint8Array(b); - HEAPU16 = new Uint16Array(b); - HEAPU32 = new Uint32Array(b); - HEAPF32 = new Float32Array(b); - HEAPF64 = new Float64Array(b); -} +var l, k, m, n, p; -wasmMemory = Module["mem"] || new WebAssembly.Memory({ +n = e.mem || new WebAssembly.Memory({ initial: 256, maximum: 256, - shared: true + shared: !0 }); -updateGlobalBufferAndViews(wasmMemory.buffer); +var q = n.buffer; -var ASM_CONSTS = { - 1916: function() { - console.log("Hello from wasm worker!"); - } -}; +l = new Int32Array(q); -function getWasmTableEntry(funcPtr) { - return wasmTable.get(funcPtr); -} - -function ___assert_fail(condition, filename, line, func) { - abort("Assertion failed: " + UTF8ToString(condition) + ", at: " + [ filename ? UTF8ToString(filename) : "unknown filename", line, func ? UTF8ToString(func) : "unknown function" ]); -} - -var _wasm_workers = {}; - -var _wasm_workers_id = 1; +k = new Uint8Array(q); -function __wasm_worker_appendToQueue(e) { - __wasm_worker_delayedMessageQueue.push(e); -} - -function __wasm_worker_runPostMessage(e) { - let data = e.data, wasmCall = data["_wsc"]; - wasmCall && getWasmTableEntry(wasmCall)(...data["x"]); -} - -function __emscripten_create_wasm_worker_with_tls(stackLowestAddress, stackSize, tlsAddress) { - let worker = _wasm_workers[_wasm_workers_id] = new Worker(Module["$wb"]); - worker.postMessage({ - $ww: _wasm_workers_id, - wasm: Module["wasm"], - js: Module["js"], - mem: wasmMemory, - sb: stackLowestAddress, - sz: stackSize, - tb: tlsAddress - }); - worker.addEventListener("message", __wasm_worker_runPostMessage); - return _wasm_workers_id++; -} +new Uint16Array(q); -var __wasm_worker_delayedMessageQueue = []; +m = new Float64Array(q); -function __wasm_worker_initializeRuntime() { - let m = Module; - _emscripten_wasm_worker_initialize(m["sb"], m["sz"], m["tb"]); - removeEventListener("message", __wasm_worker_appendToQueue); - __wasm_worker_delayedMessageQueue.forEach(__wasm_worker_runPostMessage); - __wasm_worker_delayedMessageQueue = null; - addEventListener("message", __wasm_worker_runPostMessage); -} - -var readAsmConstArgsArray = []; - -function readAsmConstArgs(sigPtr, buf) { - readAsmConstArgsArray.length = 0; - var ch; - buf >>= 2; - while (ch = HEAPU8[sigPtr++]) { - var readAsmConstArgsDouble = ch < 105; - if (readAsmConstArgsDouble && buf & 1) buf++; - readAsmConstArgsArray.push(readAsmConstArgsDouble ? HEAPF64[buf++ >> 1] : HEAP32[buf]); - ++buf; +var r = { + 1924: function() { + console.log("Hello from wasm worker!"); } - return readAsmConstArgsArray; -} - -function _emscripten_asm_const_int(code, sigPtr, argbuf) { - var args = readAsmConstArgs(sigPtr, argbuf); - return ASM_CONSTS[code].apply(null, args); -} - -function _emscripten_resize_heap(requestedSize) { - var oldSize = HEAPU8.length; - requestedSize = requestedSize >>> 0; - return false; -} - -function _emscripten_wasm_worker_post_function_v(id, funcPtr) { - _wasm_workers[id].postMessage({ - _wsc: funcPtr, - x: [] - }); -} - -if (ENVIRONMENT_IS_WASM_WORKER) { - _wasm_workers[0] = this; - addEventListener("message", __wasm_worker_appendToQueue); -} - -var asmLibraryArg = { - b: ___assert_fail, - c: __emscripten_create_wasm_worker_with_tls, - f: _emscripten_asm_const_int, - d: _emscripten_resize_heap, - e: _emscripten_wasm_worker_post_function_v, - a: wasmMemory -}; - -function run() { - var ret = _main(); -} - -function initRuntime(asm) { - if (ENVIRONMENT_IS_WASM_WORKER) return __wasm_worker_initializeRuntime(); - asm["g"](); -} - -var imports = { - a: asmLibraryArg -}; - -var _main, _emscripten_wasm_worker_initialize; - -WebAssembly.instantiate(Module["wasm"], imports).then((function(output) { - var asm = output.instance.exports; - _main = asm["h"]; - _emscripten_wasm_worker_initialize = asm["j"]; - wasmTable = asm["i"]; - initRuntime(asm); - ready(); +}, t = {}, u = 1; + +function v(a) { + w.push(a); +} + +function x(a) { + a = a.data; + let c = a._wsc; + c && p.get(c)(...a.x); +} + +var w = [], y = []; + +f && (t[0] = this, addEventListener("message", v)); + +var z, A; + +WebAssembly.instantiate(e.wasm, { + a: { + b: function(a, c, b, d) { + throw "Assertion failed: " + h(a) + ",at: " + [ c ? h(c) : "unknown filename", b, d ? h(d) : "unknown function" ]; + }, + c: function(a, c, b) { + let d = t[u] = new Worker(e.$wb); + d.postMessage({ + $ww: u, + wasm: e.wasm, + js: e.js, + mem: n, + sb: a, + sz: c, + tb: b + }); + d.addEventListener("message", x); + return u++; + }, + f: function(a, c, b) { + y.length = 0; + var d; + for (b >>= 2; d = k[c++]; ) (d = 105 > d) && b & 1 && b++, y.push(d ? m[b++ >> 1] : l[b]), + ++b; + return r[a].apply(null, y); + }, + d: function() { + return !1; + }, + e: function(a, c) { + t[a].postMessage({ + _wsc: c, + x: [] + }); + }, + a: n + } +}).then((function(a) { + a = a.instance.exports; + z = a.h; + A = a.j; + p = a.i; + f ? (a = e, A(a.sb, a.sz, a.tb), removeEventListener("message", v), w.forEach(x), + w = null, addEventListener("message", x)) : a.g(); + f || z(); })); \ No newline at end of file diff --git a/tests/code_size/hello_wasm_worker_wasm.json b/tests/code_size/hello_wasm_worker_wasm.json index 48ab60233c71..7f866cabd053 100644 --- a/tests/code_size/hello_wasm_worker_wasm.json +++ b/tests/code_size/hello_wasm_worker_wasm.json @@ -1,10 +1,10 @@ { "a.html": 737, "a.html.gz": 433, - "a.js": 3937, - "a.js.gz": 1502, - "a.wasm": 2830, - "a.wasm.gz": 1472, - "total": 7504, - "total_gz": 3407 + "a.js": 1368, + "a.js.gz": 804, + "a.wasm": 2838, + "a.wasm.gz": 1476, + "total": 4943, + "total_gz": 2713 } diff --git a/tests/test_other.py b/tests/test_other.py index 15b5960a6d1d..ed28bd027ea6 100644 --- a/tests/test_other.py +++ b/tests/test_other.py @@ -9099,7 +9099,7 @@ def test(args, closure, opt): # Windows and Mac autorollers, despite the bot being correctly configured to # skip this test in all three platforms (Linux, Mac, and Windows). # The no_windows/no_mac decorators also solve that problem. -# @no_windows("Code size is slightly different on Windows") + @no_windows("Code size is slightly different on Windows") @no_mac("Code size is slightly different on Mac") @parameterized({ 'hello_world_wasm': ('hello_world', False, True), @@ -9134,7 +9134,7 @@ def test_minimal_runtime_code_size(self, test_name, js, compare_js_output=False) '-sNO_FILESYSTEM', '--output_eol', 'linux', '-Oz', - '--closure=0', + '--closure=1', '-DNDEBUG', '-ffast-math'] From 98cc7216aa8c9e8236124002da4572cc4fe48283 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jukka=20Jyl=C3=A4nki?= Date: Thu, 24 Feb 2022 10:41:47 +0200 Subject: [PATCH 45/65] Update hello worker wasm --- tests/code_size/hello_wasm_worker_wasm.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/code_size/hello_wasm_worker_wasm.js b/tests/code_size/hello_wasm_worker_wasm.js index d87640ab118b..418d82ffbc2e 100644 --- a/tests/code_size/hello_wasm_worker_wasm.js +++ b/tests/code_size/hello_wasm_worker_wasm.js @@ -33,7 +33,7 @@ new Uint16Array(q); m = new Float64Array(q); var r = { - 1924: function() { + 1908: function() { console.log("Hello from wasm worker!"); } }, t = {}, u = 1; From 28f37d9d012a725e9002c7d5af1ad884a40756ef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jukka=20Jyl=C3=A4nki?= Date: Thu, 24 Feb 2022 12:26:06 +0200 Subject: [PATCH 46/65] flake --- emcc.py | 1 - 1 file changed, 1 deletion(-) diff --git a/emcc.py b/emcc.py index f93d8d7d2823..7f793791ee1e 100755 --- a/emcc.py +++ b/emcc.py @@ -2087,7 +2087,6 @@ def phase_linker_setup(options, state, newargs, user_settings): else: settings.JS_LIBRARIES.append((0, 'library_pthread_stub.js')) - # TODO: Move this into the library JS file once it becomes possible. # See https://github.com/emscripten-core/emscripten/pull/15982 if settings.INCLUDE_FULL_LIBRARY and not settings.DISABLE_EXCEPTION_CATCHING: From 76a4e82f5823f1f07aa0e7c3d20b79d69af1f563 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jukka=20Jyl=C3=A4nki?= Date: Thu, 3 Mar 2022 17:32:18 +0200 Subject: [PATCH 47/65] Address review: Allow building with -sSHARED_MEMORY and add a test. Move code from emcc.py to library_wasm_worker.js. --- emcc.py | 25 +++++++------------------ src/library_pthread.js | 7 +++++++ src/library_wasm_worker.js | 24 ++++++++++++++++++++++++ src/settings.js | 14 +++++++++++--- src/settings_internal.js | 3 --- tests/test_browser.py | 17 ++++++++++++++--- tests/wasm_worker/shared_memory.c | 6 ++++++ 7 files changed, 69 insertions(+), 27 deletions(-) create mode 100644 tests/wasm_worker/shared_memory.c diff --git a/emcc.py b/emcc.py index 2ab60dad5159..f541bbbc81dc 100755 --- a/emcc.py +++ b/emcc.py @@ -308,7 +308,7 @@ def setup_environment_settings(): if not settings.ENVIRONMENT_MAY_BE_WORKER and settings.PROXY_TO_WORKER: exit_with_error('If you specify --proxy-to-worker and specify a "-s ENVIRONMENT=" directive, it must include "worker" as a target! (Try e.g. -s ENVIRONMENT=web,worker)') - if not settings.ENVIRONMENT_MAY_BE_WORKER and (settings.USE_PTHREADS or settings.WASM_WORKERS): + if not settings.ENVIRONMENT_MAY_BE_WORKER and settings.SHARED_MEMORY: exit_with_error('When building with multithreading enabled and a "-s ENVIRONMENT=" directive is specified, it must include "worker" as a target! (Try e.g. -s ENVIRONMENT=web,worker)') @@ -1438,7 +1438,11 @@ def phase_setup(options, state, newargs, user_settings): if settings.MAIN_MODULE or settings.SIDE_MODULE: settings.RELOCATABLE = 1 - if (settings.USE_PTHREADS or settings.WASM_WORKERS) and '-pthread' not in newargs: + # Pthreads and Wasm Workers require targeting shared Wasm memory (SAB). + if settings.USE_PTHREADS or settings.WASM_WORKERS: + settings.SHARED_MEMORY = 1 + + if settings.SHARED_MEMORY and '-pthread' not in newargs: newargs += ['-pthread'] if 'DISABLE_EXCEPTION_CATCHING' in user_settings and 'EXCEPTION_CATCHING_ALLOWED' in user_settings: @@ -1598,10 +1602,6 @@ def phase_linker_setup(options, state, newargs, user_settings): # Requesting both Wasm and Wasm2JS support settings.WASM2JS = 1 - # Pthreads and Wasm Workers require targeting shared Wasm memory (SAB). - if settings.USE_PTHREADS or settings.WASM_WORKERS: - settings.SHARED_MEMORY = 1 - if (options.oformat == OFormat.WASM or settings.PURE_WASI) and not settings.SIDE_MODULE: # if the output is just a wasm file, it will normally be a standalone one, # as there is no JS. an exception are side modules, as we can't tell at @@ -2166,18 +2166,6 @@ def include_and_export(name): elif settings.PROXY_TO_PTHREAD: exit_with_error('-s PROXY_TO_PTHREAD=1 requires -s USE_PTHREADS to work!') - if settings.WASM_WORKERS: - if settings.SINGLE_FILE: - exit_with_error('TODO: -s SINGLE_FILE=1 is currently not supported with -s WASM_WORKERS!') - if settings.LINKABLE: - exit_with_error('TODO: -s LINKABLE=1 is currently not supported with -s WASM_WORKERS!') - if settings.SIDE_MODULE: - exit_with_error('TODO: -s SIDE_MODULE=1 is currently not supported with -s WASM_WORKERS!') - if settings.MAIN_MODULE: - exit_with_error('TODO: -s MAIN_MODULE=1 is currently not supported with -s WASM_WORKERS!') - if settings.PROXY_TO_WORKER: - exit_with_error('--proxy-to-worker is not supported with -s WASM_WORKERS!') - def check_memory_setting(setting): if settings[setting] % webassembly.WASM_PAGE_SIZE != 0: exit_with_error(f'{setting} must be a multiple of WebAssembly page size (64KiB), was {settings[setting]}') @@ -2866,6 +2854,7 @@ def phase_final_emitting(options, state, target, wasm_target, memfile): minified_worker = building.acorn_optimizer(worker_output, ['minifyWhitespace'], return_output=True) write_file(worker_output, minified_worker) + # Deploy the Wasm Worker bootstrap file as an output file (*.ww.js) if settings.WASM_WORKERS == 1: worker_output = os.path.join(target_dir, settings.WASM_WORKER_FILE) with open(worker_output, 'w') as f: diff --git a/src/library_pthread.js b/src/library_pthread.js index 83708f97e043..b81f478d1754 100644 --- a/src/library_pthread.js +++ b/src/library_pthread.js @@ -4,6 +4,13 @@ * SPDX-License-Identifier: MIT */ +#if !USE_PTHREADS +#error Internal error! USE_PTHREADS should be enabled when including library_pthread.js. +#endif +#if !SHARED_MEMORY +#error Internal error! SHARED_MEMORY should be enabled when including library_pthread.js. +#endif + var LibraryPThread = { $PThread__postset: 'PThread.init();', $PThread__deps: ['_emscripten_thread_init', diff --git a/src/library_wasm_worker.js b/src/library_wasm_worker.js index 438287b0b8d2..d760b77666dc 100644 --- a/src/library_wasm_worker.js +++ b/src/library_wasm_worker.js @@ -2,6 +2,30 @@ {{{ (function() { global.instantiateModule = function() { return MODULARIZE ? `${EXPORT_NAME}(d);` : ''; }; return null; })(); }}} {{{ (function() { global.instantiateWasm = function() { return MINIMAL_RUNTIME ? '' : 'd[`instantiateWasm`]=(i,r)=>{var n=new WebAssembly.Instance(d[`wasm`],i);r(n,d[`wasm`]);return n.exports};'; }; return null; })(); }}} +#if WASM_WORKERS + +#if !SHARED_MEMORY +#error Internal error! SHARED_MEMORY should be enabled when building with WASM_WORKERS +#endif +#if SINGLE_FILE +#error -sSINGLE_FILE is not supported with -sWASM_WORKERS! +#endif +#if LINKABLE +#error -sLINKABLE is not supported with -sWASM_WORKERS! +#endif +#if SIDE_MODULE +#error -sSIDE_MODULE is not supported with -sWASM_WORKERS! +#endif +#if MAIN_MODULE +#error -sMAIN_MODULE is not supported with -sWASM_WORKERS! +#endif +#if PROXY_TO_WORKER +#error -sPROXY_TO_WORKER is not supported with -sWASM_WORKERS! +#endif + +#endif // ~WASM_WORKERS + + mergeInto(LibraryManager.library, { wasm_workers: {}, wasm_workers_id: 1, diff --git a/src/settings.js b/src/settings.js index fdca8af4761d..6b8852c1a34c 100644 --- a/src/settings.js +++ b/src/settings.js @@ -1465,7 +1465,11 @@ var SDL2_MIXER_FORMATS = ["ogg"]; // [other] var IN_TEST_HARNESS = 0; -// If true, enables support for pthreads. +// If 1, target compiling a shared Wasm Memory. +// [compile+link] - affects user code at compile and system libraries at link. +var SHARED_MEMORY = 0; + +// If true, enables support for pthreads. This implies SHARED_MEMORY. // This setting is equivalent to `-pthread`, which should be preferred. // [compile+link] - affects user code at compile and system libraries at link. var USE_PTHREADS = 0; @@ -1475,8 +1479,12 @@ var USE_PTHREADS = 0; // of Wasm SharedArrayBuffer + Atomics API. // [compile+link] - affects user code at compile and system libraries at link. var WASM_WORKERS = 0; -// Wasm Workers options: -var WASM_WORKERS_NO_TLS = 0; // set to 1 to disable TLS for small code size gain when not using TLS. + +// Wasm Worker option: set to 1 to disable TLS for small code size gain when +// not using TLS. This setting can be used to verify that there is no leftover +// state stored in a Worker when manually implementing thread pooling in Workers. +// [link] +var WASM_WORKERS_NO_TLS = 0; // In web browsers, Workers cannot be created while the main browser thread // is executing JS/Wasm code, but the main thread must regularly yield back diff --git a/src/settings_internal.js b/src/settings_internal.js index 6515db933ae4..92bf51b76916 100644 --- a/src/settings_internal.js +++ b/src/settings_internal.js @@ -132,9 +132,6 @@ var PTHREAD_WORKER_FILE = ''; // name of the file containing the Wasm Worker *.ww.js, if relevant var WASM_WORKER_FILE = ''; -// If 1, we are building with SharedArrayBuffer as Wasm Memory. -var SHARED_MEMORY = 0; - // Base URL the source mapfile, if relevant var SOURCE_MAP_BASE = ''; diff --git a/tests/test_browser.py b/tests/test_browser.py index a1a49bcdfd06..a62c00fc0e67 100644 --- a/tests/test_browser.py +++ b/tests/test_browser.py @@ -5097,6 +5097,14 @@ def test_wasm2js_fallback_on_wasm_compilation_failure(self): def test_system(self): self.btest_exit(test_file('system.c')) + # Tests building with -sSHARED_MEMORY + @also_with_minimal_runtime + def test_shared_memory(self): + self.btest(path_from_root('tests', 'wasm_worker', 'shared_memory.c'), expected='0', args=[]) + self.btest(path_from_root('tests', 'wasm_worker', 'shared_memory.c'), expected='1', args=['-sSHARED_MEMORY']) + self.btest(path_from_root('tests', 'wasm_worker', 'shared_memory.c'), expected='1', args=['-sWASM_WORKERS']) + self.btest(path_from_root('tests', 'wasm_worker', 'shared_memory.c'), expected='1', args=['-pthread']) + # Tests the hello_wasm_worker.c documentation example code. @also_with_minimal_runtime def test_wasm_worker_hello(self): @@ -5141,7 +5149,8 @@ def test_wasm_worker_tls_wasm_assembly(self): # Tests C++11 keyword thread_local for TLS in Wasm Workers @also_with_minimal_runtime def test_wasm_worker_cpp11_thread_local(self): - self.skipTest('''wasm-ld: /b/s/w/ir/cache/builder/emscripten-releases/llvm-project/llvm/include/llvm/ADT/Optional.h:199: const T &llvm::optional_detail::OptionalStorage::getValue() const & [T = unsigned int]: Assertion `hasVal' failed. + if not WINDOWS: + self.skipTest('''wasm-ld: /b/s/w/ir/cache/builder/emscripten-releases/llvm-project/llvm/include/llvm/ADT/Optional.h:199: const T &llvm::optional_detail::OptionalStorage::getValue() const & [T = unsigned int]: Assertion `hasVal' failed. PLEASE submit a bug report to https://github.com/llvm/llvm-project/issues/ and include the crash backtrace. Stack dump: #8 0x000000000068173f lld::wasm::GlobalSymbol::getGlobalIndex() const (/root/emsdk/upstream/bin/wasm-ld+0x68173f) @@ -5156,7 +5165,8 @@ def test_wasm_worker_cpp11_thread_local(self): # Tests C11 keyword _Thread_local for TLS in Wasm Workers @also_with_minimal_runtime def test_wasm_worker_c11__Thread_local(self): - self.skipTest('''wasm-ld: /b/s/w/ir/cache/builder/emscripten-releases/llvm-project/llvm/include/llvm/ADT/Optional.h:199: const T &llvm::optional_detail::OptionalStorage::getValue() const & [T = unsigned int]: Assertion `hasVal' failed. + if not WINDOWS: + self.skipTest('''wasm-ld: /b/s/w/ir/cache/builder/emscripten-releases/llvm-project/llvm/include/llvm/ADT/Optional.h:199: const T &llvm::optional_detail::OptionalStorage::getValue() const & [T = unsigned int]: Assertion `hasVal' failed. PLEASE submit a bug report to https://github.com/llvm/llvm-project/issues/ and include the crash backtrace. Stack dump: #8 0x000000000068173f lld::wasm::GlobalSymbol::getGlobalIndex() const (/root/emsdk/upstream/bin/wasm-ld+0x68173f) @@ -5172,7 +5182,8 @@ def test_wasm_worker_c11__Thread_local(self): # Tests GCC specific extension keyword __thread for TLS in Wasm Workers @also_with_minimal_runtime def test_wasm_worker_gcc___thread(self): - self.skipTest('''wasm-ld: /b/s/w/ir/cache/builder/emscripten-releases/llvm-project/llvm/include/llvm/ADT/Optional.h:199: const T &llvm::optional_detail::OptionalStorage::getValue() const & [T = unsigned int]: Assertion `hasVal' failed. + if not WINDOWS: + self.skipTest('''wasm-ld: /b/s/w/ir/cache/builder/emscripten-releases/llvm-project/llvm/include/llvm/ADT/Optional.h:199: const T &llvm::optional_detail::OptionalStorage::getValue() const & [T = unsigned int]: Assertion `hasVal' failed. PLEASE submit a bug report to https://github.com/llvm/llvm-project/issues/ and include the crash backtrace. Stack dump: #8 0x000000000068173f lld::wasm::GlobalSymbol::getGlobalIndex() const (/root/emsdk/upstream/bin/wasm-ld+0x68173f) diff --git a/tests/wasm_worker/shared_memory.c b/tests/wasm_worker/shared_memory.c new file mode 100644 index 000000000000..d77062f14a9a --- /dev/null +++ b/tests/wasm_worker/shared_memory.c @@ -0,0 +1,6 @@ +#include + +int main() +{ + _ReportResult(EM_ASM_INT(return buffer instanceof SharedArrayBuffer), 0); +} From 34a766ea7cb2e4e8af401c6af779633cc2750496 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jukka=20Jyl=C3=A4nki?= Date: Fri, 4 Mar 2022 10:43:10 +0200 Subject: [PATCH 48/65] Remove unnecessary dynCall statements --- src/library_wasm_worker.js | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/library_wasm_worker.js b/src/library_wasm_worker.js index d760b77666dc..efd79b547389 100644 --- a/src/library_wasm_worker.js +++ b/src/library_wasm_worker.js @@ -168,13 +168,11 @@ mergeInto(LibraryManager.library, { return Module['$ww']; }, - emscripten_wasm_worker_post_function_v__deps: ['$dynCall'], emscripten_wasm_worker_post_function_v__sig: 'vii', emscripten_wasm_worker_post_function_v: function(id, funcPtr) { _wasm_workers[id].postMessage({'_wsc': funcPtr, 'x': [] }); // "WaSm Call" }, - emscripten_wasm_worker_post_function_1__deps: ['$dynCall'], emscripten_wasm_worker_post_function_1__sig: 'viid', emscripten_wasm_worker_post_function_1: function(id, funcPtr, arg0) { _wasm_workers[id].postMessage({'_wsc': funcPtr, 'x': [arg0] }); // "WaSm Call" @@ -183,7 +181,6 @@ mergeInto(LibraryManager.library, { emscripten_wasm_worker_post_function_vi: 'emscripten_wasm_worker_post_function_1', emscripten_wasm_worker_post_function_vd: 'emscripten_wasm_worker_post_function_1', - emscripten_wasm_worker_post_function_2__deps: ['$dynCall'], emscripten_wasm_worker_post_function_2__sig: 'viidd', emscripten_wasm_worker_post_function_2: function(id, funcPtr, arg0, arg1) { _wasm_workers[id].postMessage({'_wsc': funcPtr, 'x': [arg0, arg1] }); // "WaSm Call" @@ -191,7 +188,6 @@ mergeInto(LibraryManager.library, { emscripten_wasm_worker_post_function_vii: 'emscripten_wasm_worker_post_function_2', emscripten_wasm_worker_post_function_vdd: 'emscripten_wasm_worker_post_function_2', - emscripten_wasm_worker_post_function_3__deps: ['$dynCall'], emscripten_wasm_worker_post_function_3__sig: 'viiddd', emscripten_wasm_worker_post_function_3: function(id, funcPtr, arg0, arg1, arg2) { _wasm_workers[id].postMessage({'_wsc': funcPtr, 'x': [arg0, arg1, arg2] }); // "WaSm Call" From b45a7b7bdccfb9c2fc526024f5f3b0dacd763715 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jukka=20Jyl=C3=A4nki?= Date: Fri, 4 Mar 2022 10:56:37 +0200 Subject: [PATCH 49/65] Update mention of C11 and C++11 Atomics APIs --- site/source/docs/api_reference/wasm_workers.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/site/source/docs/api_reference/wasm_workers.rst b/site/source/docs/api_reference/wasm_workers.rst index cd5f8be3937e..d327d4c5f762 100644 --- a/site/source/docs/api_reference/wasm_workers.rst +++ b/site/source/docs/api_reference/wasm_workers.rst @@ -85,6 +85,7 @@ Pthreads and Wasm Workers share several similarities: * Both can use emscripten_atomic_* Atomics API, * Both can use GCC __sync_* Atomics API, + * Both can use C11 and C++11 Atomics APIs, * Both types of threads have a local stack. * Both types of threads have thread-local storage (TLS) support via ``thread_local`` (C++11), ``_Thread_local`` (C11) and ``__thread`` (GNU11) keywords. From ee31eedcc54a5a535dd411ddee2c0b89de6ac00c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jukka=20Jyl=C3=A4nki?= Date: Sun, 6 Mar 2022 16:56:24 +0200 Subject: [PATCH 50/65] Remove old code. --- emcc.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/emcc.py b/emcc.py index 05479835682b..4b01c3e09d21 100755 --- a/emcc.py +++ b/emcc.py @@ -2218,8 +2218,6 @@ def check_memory_setting(setting): exit_with_error(f'Due to collision in variable name "Module", the shell file "{options.shell_path}" is not compatible with build options "-s MODULARIZE=1 -s EXPORT_NAME=Module". Either provide your own shell file, change the name of the export to something else to avoid the name collision. (see https://github.com/emscripten-core/emscripten/issues/7950 for details)') if settings.STANDALONE_WASM: - if settings.SHARED_MEMORY: - exit_with_error('STANDALONE_WASM does not support shared memories yet') if settings.MINIMAL_RUNTIME: exit_with_error('MINIMAL_RUNTIME reduces JS size, and is incompatible with STANDALONE_WASM which focuses on ignoring JS anyhow and being 100% wasm') # the wasm must be runnable without the JS, so there cannot be anything that From 08041f13ee087b1e838d25bc7701d0f6b59ef3a0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jukka=20Jyl=C3=A4nki?= Date: Sun, 6 Mar 2022 17:07:25 +0200 Subject: [PATCH 51/65] Utilize runOnMainThread() in MINIMAL_RUNTIME ready handler. --- src/shell_minimal.js | 14 +------------- 1 file changed, 1 insertion(+), 13 deletions(-) diff --git a/src/shell_minimal.js b/src/shell_minimal.js index 9ca55e2dc379..deea7e102080 100644 --- a/src/shell_minimal.js +++ b/src/shell_minimal.js @@ -115,19 +115,7 @@ function ready() { readyPromiseResolve(Module); #endif // MODULARIZE #if INVOKE_RUN && HAS_MAIN -#if USE_PTHREADS - if (!ENVIRONMENT_IS_PTHREAD) { -#endif -#if WASM_WORKERS - if (!ENVIRONMENT_IS_WASM_WORKER) { -#endif - run(); -#if USE_PTHREADS - } -#endif -#if WASM_WORKERS - } -#endif + {{{ runOnMainThread("run();") }}} #else #if ASSERTIONS console.log('ready() called, and INVOKE_RUN=0. The runtime is now ready for you to call run() to invoke application _main(). You can also override ready() in a --pre-js file to get this signal as a callback') From 1f5d6b0aebbb114da402bf46cc101ab663387890 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jukka=20Jyl=C3=A4nki?= Date: Sun, 6 Mar 2022 17:09:03 +0200 Subject: [PATCH 52/65] Simplify code --- src/shell_minimal.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/shell_minimal.js b/src/shell_minimal.js index deea7e102080..d9e810400383 100644 --- a/src/shell_minimal.js +++ b/src/shell_minimal.js @@ -116,11 +116,9 @@ function ready() { #endif // MODULARIZE #if INVOKE_RUN && HAS_MAIN {{{ runOnMainThread("run();") }}} -#else -#if ASSERTIONS +#elif ASSERTIONS console.log('ready() called, and INVOKE_RUN=0. The runtime is now ready for you to call run() to invoke application _main(). You can also override ready() in a --pre-js file to get this signal as a callback') #endif -#endif } #if POLYFILL From 9e56c85a10ec56b0f34ffb9287ca02967df02672 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jukka=20Jyl=C3=A4nki?= Date: Sun, 6 Mar 2022 17:10:24 +0200 Subject: [PATCH 53/65] #error quotes --- src/library_pthread.js | 2 +- src/library_wasm_worker.js | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/library_pthread.js b/src/library_pthread.js index dea150691b8a..73ccabe004ef 100644 --- a/src/library_pthread.js +++ b/src/library_pthread.js @@ -8,7 +8,7 @@ #error "Internal error! USE_PTHREADS should be enabled when including library_pthread.js." #endif #if !SHARED_MEMORY -#error "Internal error! SHARED_MEMORY should be enabled when including library_pthread.js."" +#error "Internal error! SHARED_MEMORY should be enabled when including library_pthread.js." #endif #if USE_PTHREADS == 2 #error "USE_PTHREADS=2 is no longer supported" diff --git a/src/library_wasm_worker.js b/src/library_wasm_worker.js index efd79b547389..ba61dff90a1e 100644 --- a/src/library_wasm_worker.js +++ b/src/library_wasm_worker.js @@ -5,22 +5,22 @@ #if WASM_WORKERS #if !SHARED_MEMORY -#error Internal error! SHARED_MEMORY should be enabled when building with WASM_WORKERS +#error "Internal error! SHARED_MEMORY should be enabled when building with WASM_WORKERS" #endif #if SINGLE_FILE -#error -sSINGLE_FILE is not supported with -sWASM_WORKERS! +#error "-sSINGLE_FILE is not supported with -sWASM_WORKERS!" #endif #if LINKABLE -#error -sLINKABLE is not supported with -sWASM_WORKERS! +#error "-sLINKABLE is not supported with -sWASM_WORKERS!" #endif #if SIDE_MODULE -#error -sSIDE_MODULE is not supported with -sWASM_WORKERS! +#error "-sSIDE_MODULE is not supported with -sWASM_WORKERS!" #endif #if MAIN_MODULE -#error -sMAIN_MODULE is not supported with -sWASM_WORKERS! +#error "-sMAIN_MODULE is not supported with -sWASM_WORKERS!" #endif #if PROXY_TO_WORKER -#error -sPROXY_TO_WORKER is not supported with -sWASM_WORKERS! +#error "-sPROXY_TO_WORKER is not supported with -sWASM_WORKERS!" #endif #endif // ~WASM_WORKERS From bca824e6ef8b6141590a6623c1bdeb292d130260 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jukka=20Jyl=C3=A4nki?= Date: Sun, 6 Mar 2022 17:13:05 +0200 Subject: [PATCH 54/65] Clean typo --- src/wasm_worker.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/wasm_worker.js b/src/wasm_worker.js index 8702f2595d2b..3ab43fcbd4b8 100644 --- a/src/wasm_worker.js +++ b/src/wasm_worker.js @@ -1,4 +1,4 @@ -// N.B. The contents of this file are duplicated in src/library_wasm_workers.js +// N.B. The contents of this file are duplicated in src/library_wasm_worker.js // in variable "_wasmWorkerBlobUrl" (where the contents are pre-minified) If // doing any changes to this file, be sure to update the contents there too. onmessage = function(d) { From 07a3585aa2b4e1f66be2949e78777ef6155da02a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jukka=20Jyl=C3=A4nki?= Date: Sun, 6 Mar 2022 17:16:42 +0200 Subject: [PATCH 55/65] Cleanup tests --- tests/test_browser.py | 16 +--------------- 1 file changed, 1 insertion(+), 15 deletions(-) diff --git a/tests/test_browser.py b/tests/test_browser.py index 4c24eac33dab..1d5502051ca5 100644 --- a/tests/test_browser.py +++ b/tests/test_browser.py @@ -22,7 +22,7 @@ from common import BrowserCore, RunnerCore, path_from_root, has_browser, EMTEST_BROWSER, Reporting from common import create_file, parameterized, ensure_dir, disabled, test_file, WEBIDL_BINDER -from common import read_file, require_v8 +from common import read_file, require_v8, also_with_minimal_runtime from tools import shared from tools import ports from tools.shared import EMCC, WINDOWS, FILE_PACKAGER, PIPE @@ -104,20 +104,6 @@ def metafunc(self, with_wasm2js): return metafunc -def also_with_minimal_runtime(f): - assert callable(f) - - def metafunc(self, with_minimal_runtime): - assert self.get_setting('MINIMAL_RUNTIME') is None - if with_minimal_runtime: - self.set_setting('MINIMAL_RUNTIME', 1) - f(self) - - metafunc._parameterize = {'': (False,), - 'minimal_runtime': (True,)} - return metafunc - - def shell_with_script(shell_file, output_file, replacement): shell = read_file(path_from_root('src', shell_file)) create_file(output_file, shell.replace('{{{ SCRIPT }}}', replacement)) From 7fa8dd7481862d8bb73100ab35a2edbb740613e6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jukka=20Jyl=C3=A4nki?= Date: Sun, 6 Mar 2022 17:21:59 +0200 Subject: [PATCH 56/65] Update ChangeLog --- ChangeLog.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/ChangeLog.md b/ChangeLog.md index a711f179fd9b..d7cc6d128c75 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -26,6 +26,10 @@ See docs/process.md for more on how version tagging works. - Fix deadlock in `munmap` that was introduced in 3.1.5. The deadlock would occur in multi-threaded programs when a partial unmap was requested (which emscripten does not support). (#16413) +- Added new compiler+linker option -sSHARED_MEMORY=1, which enables targeting + a shared WebAssembly.Memory. (#16419) +- Added new API "Wasm Workers", which is an alternative to pthreads for building + multithreaded applications, enabled via -sWASM_WORKERS=1 (#12833) 3.1.6 - 02/24/2022 ------------------ From 33079fdc167728dd1299fa73f7ed3254941d51fc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jukka=20Jyl=C3=A4nki?= Date: Sun, 6 Mar 2022 17:43:30 +0200 Subject: [PATCH 57/65] Fixes --- emcc.py | 2 +- site/source/docs/api_reference/wasm_workers.rst | 4 ++-- src/library_pthread_stub.js | 6 +++--- tests/test_other.py | 12 +++++++++++- 4 files changed, 17 insertions(+), 7 deletions(-) diff --git a/emcc.py b/emcc.py index 4b01c3e09d21..4260366a3e06 100755 --- a/emcc.py +++ b/emcc.py @@ -1432,7 +1432,7 @@ def phase_setup(options, state, newargs, user_settings): settings.SHARED_MEMORY = 1 if settings.SHARED_MEMORY and '-pthread' not in newargs: - newargs += ['-pthread'] + newargs += ['-matomics', '-mbulk-memory'] if 'DISABLE_EXCEPTION_CATCHING' in user_settings and 'EXCEPTION_CATCHING_ALLOWED' in user_settings: # If we get here then the user specified both DISABLE_EXCEPTION_CATCHING and EXCEPTION_CATCHING_ALLOWED diff --git a/site/source/docs/api_reference/wasm_workers.rst b/site/source/docs/api_reference/wasm_workers.rst index d327d4c5f762..7278f7539f4d 100644 --- a/site/source/docs/api_reference/wasm_workers.rst +++ b/site/source/docs/api_reference/wasm_workers.rst @@ -300,8 +300,8 @@ table. Compile and link with -sWASM_WORKERS=1 Preprocessor directives - __EMSCRIPTEN_PTHREADS__=1 and __EMSCRIPTEN_SHARED_MEMORY__=1 are active - __EMSCRIPTEN_PTHREADS__=1, __EMSCRIPTEN_SHARED_MEMORY__=1 and __EMSCRIPTEN_WASM_WORKERS__=1 are active + __EMSCRIPTEN_SHARED_MEMORY__=1 and __EMSCRIPTEN_PTHREADS__=1 are active + __EMSCRIPTEN_SHARED_MEMORY__=1 and __EMSCRIPTEN_WASM_WORKERS__=1 are active JS library directives USE_PTHREADS=1 and SHARED_MEMORY=1 are active diff --git a/src/library_pthread_stub.js b/src/library_pthread_stub.js index bb1de897bfd3..b18ef09eafb7 100644 --- a/src/library_pthread_stub.js +++ b/src/library_pthread_stub.js @@ -7,9 +7,9 @@ #if USE_PTHREADS #error "Internal error! USE_PTHREADS should not be enabled when including library_pthread_stub.js." #endif -//#if STANDALONE_WASM && SHARED_MEMORY -//#error "STANDALONE_WASM does not support shared memories yet" -//#endif +#if STANDALONE_WASM && SHARED_MEMORY +#error "STANDALONE_WASM does not support shared memories yet" +#endif var LibraryPThreadStub = { // =================================================================================== diff --git a/tests/test_other.py b/tests/test_other.py index ba0d37c515ca..9b7922bc4891 100644 --- a/tests/test_other.py +++ b/tests/test_other.py @@ -11790,5 +11790,15 @@ def test_shared_memory(self): self.do_runf(test_file('wasm_worker/shared_memory.c'), '0', emcc_args=[]) self.node_args += ['--experimental-wasm-threads', '--experimental-wasm-bulk-memory'] self.do_runf(test_file('wasm_worker/shared_memory.c'), '1', emcc_args=['-sSHARED_MEMORY']) -# self.do_runf(test_file('wasm_worker/shared_memory.c'), '1', emcc_args=['-sWASM_WORKERS']) + self.do_runf(test_file('wasm_worker/shared_memory.c'), '1', emcc_args=['-sWASM_WORKERS']) self.do_runf(test_file('wasm_worker/shared_memory.c'), '1', emcc_args=['-pthread']) + + # Tests C preprocessor flags with -sSHARED_MEMORY + @also_with_minimal_runtime + def test_shared_memory_preprocessor_flags(self): + self.run_process([EMCC, '-c', test_file('wasm_worker/shared_memory_preprocessor_flags.c'), '-sSHARED_MEMORY']) + + # Tests C preprocessor flags with -sWASM_WORKERS + @also_with_minimal_runtime + def test_wasm_worker_preprocessor_flags(self): + self.run_process([EMCC, '-c', test_file('wasm_worker/wasm_worker_preprocessor_flags.c'), '-sWASM_WORKERS']) From 4f8dc6cde04de7865208f596e2bef100e23ded83 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jukka=20Jyl=C3=A4nki?= Date: Sun, 6 Mar 2022 17:48:41 +0200 Subject: [PATCH 58/65] Add test files. --- .../wasm_worker/shared_memory_preprocessor_flags.c | 13 +++++++++++++ tests/wasm_worker/wasm_worker_preprocessor_flags.c | 13 +++++++++++++ 2 files changed, 26 insertions(+) create mode 100644 tests/wasm_worker/shared_memory_preprocessor_flags.c create mode 100644 tests/wasm_worker/wasm_worker_preprocessor_flags.c diff --git a/tests/wasm_worker/shared_memory_preprocessor_flags.c b/tests/wasm_worker/shared_memory_preprocessor_flags.c new file mode 100644 index 000000000000..5a268f484de1 --- /dev/null +++ b/tests/wasm_worker/shared_memory_preprocessor_flags.c @@ -0,0 +1,13 @@ +// This file should be compiled with -sSHARED_MEMORY=1 + +#ifndef __EMSCRIPTEN_SHARED_MEMORY__ +#error __EMSCRIPTEN_SHARED_MEMORY__ should be defined when building with -sSHARED_MEMORY=1! +#endif + +#ifdef __EMSCRIPTEN_WASM_WORKERS__ +#error __EMSCRIPTEN_WASM_WORKERS__ should not defined when building with -sSHARED_MEMORY=1! +#endif + +#ifdef __EMSCRIPTEN_PTHREADS__ +#error __EMSCRIPTEN_PTHREADS__ should not be defined when building with -sSHARED_MEMORY=1! +#endif diff --git a/tests/wasm_worker/wasm_worker_preprocessor_flags.c b/tests/wasm_worker/wasm_worker_preprocessor_flags.c new file mode 100644 index 000000000000..1e9d157f20e0 --- /dev/null +++ b/tests/wasm_worker/wasm_worker_preprocessor_flags.c @@ -0,0 +1,13 @@ +// This file should be compiled with -sWASM_WORKERS=1 + +#ifndef __EMSCRIPTEN_SHARED_MEMORY__ +#error __EMSCRIPTEN_SHARED_MEMORY__ should be defined when building with -sWASM_WORKERS=1! +#endif + +#ifndef __EMSCRIPTEN_WASM_WORKERS__ +#error __EMSCRIPTEN_WASM_WORKERS__ should be defined when building with -sWASM_WORKERS=1! +#endif + +#ifdef __EMSCRIPTEN_PTHREADS__ +#error __EMSCRIPTEN_PTHREADS__ should not be defined when building with -sWASM_WORKERS=1! +#endif From c78609d23ca730fa07fffc1cddc62cff3562e598 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jukka=20Jyl=C3=A4nki?= Date: Sun, 6 Mar 2022 19:11:29 +0200 Subject: [PATCH 59/65] Fix pthreads --- emcc.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/emcc.py b/emcc.py index 4260366a3e06..253d3d9b3fda 100755 --- a/emcc.py +++ b/emcc.py @@ -1431,8 +1431,14 @@ def phase_setup(options, state, newargs, user_settings): if settings.USE_PTHREADS or settings.WASM_WORKERS: settings.SHARED_MEMORY = 1 - if settings.SHARED_MEMORY and '-pthread' not in newargs: - newargs += ['-matomics', '-mbulk-memory'] + if settings.SHARED_MEMORY: + if '-matomics' not in newargs: + newargs += ['-matomics'] + if '-mbulk-memory' not in newargs: + newargs += ['-mbulk-memory'] + + if settings.USE_PTHREADS and '-pthread' not in newargs: + newargs += ['-pthread'] if 'DISABLE_EXCEPTION_CATCHING' in user_settings and 'EXCEPTION_CATCHING_ALLOWED' in user_settings: # If we get here then the user specified both DISABLE_EXCEPTION_CATCHING and EXCEPTION_CATCHING_ALLOWED From 552e0a770e64980408f6549711d9495a1cf75246 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jukka=20Jyl=C3=A4nki?= Date: Mon, 7 Mar 2022 10:55:30 +0200 Subject: [PATCH 60/65] Remove moved test --- tests/test_browser.py | 8 -------- 1 file changed, 8 deletions(-) diff --git a/tests/test_browser.py b/tests/test_browser.py index 1d5502051ca5..044637f8757c 100644 --- a/tests/test_browser.py +++ b/tests/test_browser.py @@ -5083,14 +5083,6 @@ def test_wasm2js_fallback_on_wasm_compilation_failure(self): def test_system(self): self.btest_exit(test_file('system.c')) - # Tests building with -sSHARED_MEMORY - @also_with_minimal_runtime - def test_shared_memory(self): - self.btest(path_from_root('tests', 'wasm_worker', 'shared_memory.c'), expected='0', args=[]) - self.btest(path_from_root('tests', 'wasm_worker', 'shared_memory.c'), expected='1', args=['-sSHARED_MEMORY']) - self.btest(path_from_root('tests', 'wasm_worker', 'shared_memory.c'), expected='1', args=['-sWASM_WORKERS']) - self.btest(path_from_root('tests', 'wasm_worker', 'shared_memory.c'), expected='1', args=['-pthread']) - # Tests the hello_wasm_worker.c documentation example code. @also_with_minimal_runtime def test_wasm_worker_hello(self): From 7cfeb98219c0028d336afd34d179854229ee7445 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jukka=20Jyl=C3=A4nki?= Date: Mon, 7 Mar 2022 11:07:04 +0200 Subject: [PATCH 61/65] Address review --- emcc.py | 6 +- .../wasm_worker/library_wasm_worker_stub.c | 95 ++++++++----------- tests/test_browser.py | 68 ++++++------- tools/system_libs.py | 2 + 4 files changed, 76 insertions(+), 95 deletions(-) diff --git a/emcc.py b/emcc.py index 253d3d9b3fda..779d5191f93b 100755 --- a/emcc.py +++ b/emcc.py @@ -1431,14 +1431,14 @@ def phase_setup(options, state, newargs, user_settings): if settings.USE_PTHREADS or settings.WASM_WORKERS: settings.SHARED_MEMORY = 1 - if settings.SHARED_MEMORY: + if settings.USE_PTHREADS and '-pthread' not in newargs: + newargs += ['-pthread'] + elif settings.SHARED_MEMORY: if '-matomics' not in newargs: newargs += ['-matomics'] if '-mbulk-memory' not in newargs: newargs += ['-mbulk-memory'] - if settings.USE_PTHREADS and '-pthread' not in newargs: - newargs += ['-pthread'] if 'DISABLE_EXCEPTION_CATCHING' in user_settings and 'EXCEPTION_CATCHING_ALLOWED' in user_settings: # If we get here then the user specified both DISABLE_EXCEPTION_CATCHING and EXCEPTION_CATCHING_ALLOWED diff --git a/system/lib/wasm_worker/library_wasm_worker_stub.c b/system/lib/wasm_worker/library_wasm_worker_stub.c index a8a257a7c46c..131ba6392535 100644 --- a/system/lib/wasm_worker/library_wasm_worker_stub.c +++ b/system/lib/wasm_worker/library_wasm_worker_stub.c @@ -8,106 +8,85 @@ #error __EMSCRIPTEN_WASM_WORKERS__ should not be defined when building this file! #endif -emscripten_wasm_worker_t emscripten_create_wasm_worker_with_tls(void *stackLowestAddress, uint32_t stackSize, void *tlsAddress, uint32_t tlsSize) -{ - return 0; +emscripten_wasm_worker_t emscripten_create_wasm_worker_with_tls(void *stackLowestAddress, uint32_t stackSize, void *tlsAddress, uint32_t tlsSize) { + return 0; } -emscripten_wasm_worker_t emscripten_create_wasm_worker_no_tls(void *stackLowestAddress, uint32_t stackSize) -{ - return 0; +emscripten_wasm_worker_t emscripten_create_wasm_worker_no_tls(void *stackLowestAddress, uint32_t stackSize) { + return 0; } -emscripten_wasm_worker_t emscripten_malloc_wasm_worker(uint32_t stackSize) -{ - return 0; +emscripten_wasm_worker_t emscripten_malloc_wasm_worker(uint32_t stackSize) { + return 0; } -void emscripten_wasm_worker_sleep(int64_t nsecs) -{ +void emscripten_wasm_worker_sleep(int64_t nsecs) { } -void emscripten_lock_init(emscripten_lock_t *lock) -{ +void emscripten_lock_init(emscripten_lock_t *lock) { } -EM_BOOL emscripten_lock_wait_acquire(emscripten_lock_t *lock, int64_t maxWaitNanoseconds) -{ - return EM_TRUE; +EM_BOOL emscripten_lock_wait_acquire(emscripten_lock_t *lock, int64_t maxWaitNanoseconds) { + return EM_TRUE; } -void emscripten_lock_waitinf_acquire(emscripten_lock_t *lock) -{ +void emscripten_lock_waitinf_acquire(emscripten_lock_t *lock) { } -EM_BOOL emscripten_lock_busyspin_wait_acquire(emscripten_lock_t *lock, double maxWaitMilliseconds) -{ - return EM_TRUE; +EM_BOOL emscripten_lock_busyspin_wait_acquire(emscripten_lock_t *lock, double maxWaitMilliseconds) { + return EM_TRUE; } -void emscripten_lock_busyspin_waitinf_acquire(emscripten_lock_t *lock) -{ +void emscripten_lock_busyspin_waitinf_acquire(emscripten_lock_t *lock) { } -EM_BOOL emscripten_lock_try_acquire(emscripten_lock_t *lock) -{ - return EM_TRUE; +EM_BOOL emscripten_lock_try_acquire(emscripten_lock_t *lock) { + return EM_TRUE; } -void emscripten_lock_release(emscripten_lock_t *lock) -{ +void emscripten_lock_release(emscripten_lock_t *lock) { } -void emscripten_semaphore_init(emscripten_semaphore_t *sem, int num) -{ +void emscripten_semaphore_init(emscripten_semaphore_t *sem, int num) { } -int emscripten_semaphore_try_acquire(emscripten_semaphore_t *sem, int num) -{ - *sem -= num; - return *sem; +int emscripten_semaphore_try_acquire(emscripten_semaphore_t *sem, int num) { + *sem -= num; + return *sem; } -int emscripten_semaphore_wait_acquire(emscripten_semaphore_t *sem, int num, int64_t maxWaitNanoseconds) -{ - *sem -= num; - return *sem; +int emscripten_semaphore_wait_acquire(emscripten_semaphore_t *sem, int num, int64_t maxWaitNanoseconds) { + *sem -= num; + return *sem; } -int emscripten_semaphore_waitinf_acquire(emscripten_semaphore_t *sem, int num) -{ - *sem -= num; - return *sem; +int emscripten_semaphore_waitinf_acquire(emscripten_semaphore_t *sem, int num) { + *sem -= num; + return *sem; } -uint32_t emscripten_semaphore_release(emscripten_semaphore_t *sem, int num) -{ - *sem += num; - return *sem - num; +uint32_t emscripten_semaphore_release(emscripten_semaphore_t *sem, int num) { + *sem += num; + return *sem - num; } -void emscripten_condvar_init(emscripten_condvar_t *condvar) -{ +void emscripten_condvar_init(emscripten_condvar_t *condvar) { } -void emscripten_condvar_waitinf(emscripten_condvar_t *condvar, emscripten_lock_t *lock) -{ +void emscripten_condvar_waitinf(emscripten_condvar_t *condvar, emscripten_lock_t *lock) { } -int emscripten_condvar_wait(emscripten_condvar_t *condvar, emscripten_lock_t *lock, int64_t maxWaitNanoseconds) -{ - return EM_TRUE; +int emscripten_condvar_wait(emscripten_condvar_t *condvar, emscripten_lock_t *lock, int64_t maxWaitNanoseconds) { + return EM_TRUE; } ATOMICS_WAIT_TOKEN_T emscripten_condvar_wait_async(emscripten_condvar_t *condvar, emscripten_lock_t *lock, void (*asyncWaitFinished)(int32_t *address, uint32_t value, ATOMICS_WAIT_RESULT_T waitResult, void *userData), void *userData, - double maxWaitMilliseconds) -{ - return 0; + double maxWaitMilliseconds) { + return 0; } -void emscripten_condvar_signal(emscripten_condvar_t *condvar, int64_t numWaitersToSignal) -{ +void emscripten_condvar_signal(emscripten_condvar_t *condvar, int64_t numWaitersToSignal) { } diff --git a/tests/test_browser.py b/tests/test_browser.py index 044637f8757c..014a924bb93f 100644 --- a/tests/test_browser.py +++ b/tests/test_browser.py @@ -5086,43 +5086,43 @@ def test_system(self): # Tests the hello_wasm_worker.c documentation example code. @also_with_minimal_runtime def test_wasm_worker_hello(self): - self.btest(path_from_root('tests', 'wasm_worker', 'hello_wasm_worker.c'), expected='0', args=['-sWASM_WORKERS']) + self.btest(test_file('wasm_worker/hello_wasm_worker.c'), expected='0', args=['-sWASM_WORKERS']) # Tests the WASM_WORKERS=2 build mode, which embeds the Wasm Worker bootstrap JS script file to the main JS file. @also_with_minimal_runtime def test_wasm_worker_embedded(self): - self.btest(path_from_root('tests', 'wasm_worker', 'hello_wasm_worker.c'), expected='0', args=['-sWASM_WORKERS=2']) + self.btest(test_file('wasm_worker/hello_wasm_worker.c'), expected='0', args=['-sWASM_WORKERS=2']) # Tests Wasm Worker thread stack setup @also_with_minimal_runtime def test_wasm_worker_thread_stack(self): - self.btest(path_from_root('tests', 'wasm_worker', 'thread_stack.c'), expected='0', args=['-sWASM_WORKERS']) + self.btest(test_file('wasm_worker/thread_stack.c'), expected='0', args=['-sWASM_WORKERS']) # Tests Wasm Worker thread stack setup without TLS support active @also_with_minimal_runtime def test_wasm_worker_thread_stack_no_tls(self): - self.btest(path_from_root('tests', 'wasm_worker', 'thread_stack.c'), expected='0', args=['-sWASM_WORKERS', '-sWASM_WORKERS_NO_TLS']) + self.btest(test_file('wasm_worker/thread_stack.c'), expected='0', args=['-sWASM_WORKERS', '-sWASM_WORKERS_NO_TLS']) # Tests emscripten_malloc_wasm_worker() and emscripten_current_thread_is_wasm_worker() functions @also_with_minimal_runtime def test_wasm_worker_malloc(self): - self.btest(path_from_root('tests', 'wasm_worker', 'malloc_wasm_worker.c'), expected='0', args=['-sWASM_WORKERS']) + self.btest(test_file('wasm_worker/malloc_wasm_worker.c'), expected='0', args=['-sWASM_WORKERS']) # Tests Wasm Worker+pthreads simultaneously @also_with_minimal_runtime def test_wasm_worker_and_pthreads(self): - self.btest(path_from_root('tests', 'wasm_worker', 'wasm_worker_and_pthread.c'), expected='0', args=['-sWASM_WORKERS', '-pthread']) + self.btest(test_file('wasm_worker/wasm_worker_and_pthread.c'), expected='0', args=['-sWASM_WORKERS', '-pthread']) # Tests emscripten_wasm_worker_self_id() function @also_with_minimal_runtime def test_wasm_worker_self_id(self): - self.btest(path_from_root('tests', 'wasm_worker', 'wasm_worker_self_id.c'), expected='0', args=['-sWASM_WORKERS']) + self.btest(test_file('wasm_worker/wasm_worker_self_id.c'), expected='0', args=['-sWASM_WORKERS']) # Tests direct Wasm Assembly .S file based TLS variables in Wasm Workers @also_with_minimal_runtime def test_wasm_worker_tls_wasm_assembly(self): - self.btest(path_from_root('tests', 'wasm_worker', 'wasm_worker_tls_wasm_assembly.c'), - expected='42', args=['-sWASM_WORKERS', path_from_root('tests', 'wasm_worker', 'wasm_worker_tls_wasm_assembly.S')]) + self.btest(test_file('wasm_worker/wasm_worker_tls_wasm_assembly.c'), + expected='42', args=['-sWASM_WORKERS', test_file('wasm_worker/wasm_worker_tls_wasm_assembly.S')]) # Tests C++11 keyword thread_local for TLS in Wasm Workers @also_with_minimal_runtime @@ -5138,7 +5138,7 @@ def test_wasm_worker_cpp11_thread_local(self): #12 0x0000000000662f89 lld::wasm::(anonymous namespace)::LinkerDriver::linkerMain(llvm::ArrayRef) Driver.cpp:0:0 #13 0x000000000065dd5e lld::wasm::link(llvm::ArrayRef, llvm::raw_ostream&, llvm::raw_ostream&, bool, bool) (/root/emsdk/upstream/bin/wasm-ld+0x65dd5e) #14 0x000000000036f799 lldMain(int, char const**, llvm::raw_ostream&, llvm::raw_ostream&, bool) lld.cpp:0:0''') - self.btest(path_from_root('tests', 'wasm_worker', 'cpp11_thread_local.cpp'), expected='42', args=['-sWASM_WORKERS']) + self.btest(test_file('wasm_worker/cpp11_thread_local.cpp'), expected='42', args=['-sWASM_WORKERS']) # Tests C11 keyword _Thread_local for TLS in Wasm Workers @also_with_minimal_runtime @@ -5155,7 +5155,7 @@ def test_wasm_worker_c11__Thread_local(self): #13 0x000000000065dd5e lld::wasm::link(llvm::ArrayRef, llvm::raw_ostream&, llvm::raw_ostream&, bool, bool) (/root/emsdk/upstream/bin/wasm-ld+0x65dd5e) #14 0x000000000036f799 lldMain(int, char const**, llvm::raw_ostream&, llvm::raw_ostream&, bool) lld.cpp:0:0''') - self.btest(path_from_root('tests', 'wasm_worker', 'c11__Thread_local.c'), expected='42', args=['-sWASM_WORKERS', '-std=gnu11']) # Cannot test C11 - because of EM_ASM must test Gnu11. + self.btest(test_file('wasm_worker/c11__Thread_local.c'), expected='42', args=['-sWASM_WORKERS', '-std=gnu11']) # Cannot test C11 - because of EM_ASM must test Gnu11. # Tests GCC specific extension keyword __thread for TLS in Wasm Workers @also_with_minimal_runtime @@ -5171,114 +5171,114 @@ def test_wasm_worker_gcc___thread(self): #12 0x0000000000662f89 lld::wasm::(anonymous namespace)::LinkerDriver::linkerMain(llvm::ArrayRef) Driver.cpp:0:0 #13 0x000000000065dd5e lld::wasm::link(llvm::ArrayRef, llvm::raw_ostream&, llvm::raw_ostream&, bool, bool) (/root/emsdk/upstream/bin/wasm-ld+0x65dd5e) #14 0x000000000036f799 lldMain(int, char const**, llvm::raw_ostream&, llvm::raw_ostream&, bool) lld.cpp:0:0''') - self.btest(path_from_root('tests', 'wasm_worker', 'gcc___Thread.c'), expected='42', args=['-sWASM_WORKERS', '-std=gnu11']) + self.btest(test_file('wasm_worker/gcc___Thread.c'), expected='42', args=['-sWASM_WORKERS', '-std=gnu11']) # Tests emscripten_wasm_worker_sleep() @also_with_minimal_runtime def test_wasm_worker_sleep(self): - self.btest(path_from_root('tests', 'wasm_worker', 'wasm_worker_sleep.c'), expected='1', args=['-sWASM_WORKERS']) + self.btest(test_file('wasm_worker/wasm_worker_sleep.c'), expected='1', args=['-sWASM_WORKERS']) # Tests emscripten_terminate_wasm_worker() @also_with_minimal_runtime def test_wasm_worker_terminate(self): - self.btest(path_from_root('tests', 'wasm_worker', 'terminate_wasm_worker.c'), expected='0', args=['-sWASM_WORKERS']) + self.btest(test_file('wasm_worker/terminate_wasm_worker.c'), expected='0', args=['-sWASM_WORKERS']) # Tests emscripten_terminate_all_wasm_workers() @also_with_minimal_runtime def test_wasm_worker_terminate_all(self): - self.btest(path_from_root('tests', 'wasm_worker', 'terminate_all_wasm_workers.c'), expected='0', args=['-sWASM_WORKERS']) + self.btest(test_file('wasm_worker/terminate_all_wasm_workers.c'), expected='0', args=['-sWASM_WORKERS']) # Tests emscripten_wasm_worker_post_function_*() API @also_with_minimal_runtime def test_wasm_worker_post_function(self): - self.btest(path_from_root('tests', 'wasm_worker', 'post_function.c'), expected='8', args=['-sWASM_WORKERS']) + self.btest(test_file('wasm_worker/post_function.c'), expected='8', args=['-sWASM_WORKERS']) # Tests emscripten_wasm_worker_post_function_*() API and EMSCRIPTEN_WASM_WORKER_ID_PARENT # to send a message back from Worker to its parent thread. @also_with_minimal_runtime def test_wasm_worker_post_function_to_main_thread(self): - self.btest(path_from_root('tests', 'wasm_worker', 'post_function_to_main_thread.c'), expected='10', args=['-sWASM_WORKERS']) + self.btest(test_file('wasm_worker/post_function_to_main_thread.c'), expected='10', args=['-sWASM_WORKERS']) # Tests emscripten_navigator_hardware_concurrency() and emscripten_atomics_is_lock_free() @also_with_minimal_runtime def test_wasm_worker_hardware_concurrency_is_lock_free(self): - self.btest(path_from_root('tests', 'wasm_worker', 'hardware_concurrency_is_lock_free.c'), expected='0', args=['-sWASM_WORKERS']) + self.btest(test_file('wasm_worker/hardware_concurrency_is_lock_free.c'), expected='0', args=['-sWASM_WORKERS']) # Tests emscripten_wasm_wait_i32() and emscripten_wasm_notify() functions. @also_with_minimal_runtime def test_wasm_worker_wait32_notify(self): - self.btest(path_from_root('tests', 'wasm_worker', 'wait32_notify.c'), expected='2', args=['-sWASM_WORKERS']) + self.btest(test_file('wasm_worker/wait32_notify.c'), expected='2', args=['-sWASM_WORKERS']) # Tests emscripten_wasm_wait_i64() and emscripten_wasm_notify() functions. @also_with_minimal_runtime def test_wasm_worker_wait64_notify(self): - self.btest(path_from_root('tests', 'wasm_worker', 'wait64_notify.c'), expected='2', args=['-sWASM_WORKERS']) + self.btest(test_file('wasm_worker/wait64_notify.c'), expected='2', args=['-sWASM_WORKERS']) # Tests emscripten_atomic_wait_async() function. @also_with_minimal_runtime def test_wasm_worker_wait_async(self): - self.btest(path_from_root('tests', 'wasm_worker', 'wait_async.c'), expected='0', args=['-sWASM_WORKERS']) + self.btest(test_file('wasm_worker/wait_async.c'), expected='0', args=['-sWASM_WORKERS']) # Tests emscripten_atomic_cancel_wait_async() function. @also_with_minimal_runtime def test_wasm_worker_cancel_wait_async(self): - self.btest(path_from_root('tests', 'wasm_worker', 'cancel_wait_async.c'), expected='1', args=['-sWASM_WORKERS']) + self.btest(test_file('wasm_worker/cancel_wait_async.c'), expected='1', args=['-sWASM_WORKERS']) # Tests emscripten_atomic_cancel_all_wait_asyncs() function. @also_with_minimal_runtime def test_wasm_worker_cancel_all_wait_asyncs(self): - self.btest(path_from_root('tests', 'wasm_worker', 'cancel_all_wait_asyncs.c'), expected='1', args=['-sWASM_WORKERS']) + self.btest(test_file('wasm_worker/cancel_all_wait_asyncs.c'), expected='1', args=['-sWASM_WORKERS']) # Tests emscripten_atomic_cancel_all_wait_asyncs_at_address() function. @also_with_minimal_runtime def test_wasm_worker_cancel_all_wait_asyncs_at_address(self): - self.btest(path_from_root('tests', 'wasm_worker', 'cancel_all_wait_asyncs_at_address.c'), expected='1', args=['-sWASM_WORKERS']) + self.btest(test_file('wasm_worker/cancel_all_wait_asyncs_at_address.c'), expected='1', args=['-sWASM_WORKERS']) # Tests emscripten_lock_init(), emscripten_lock_waitinf_acquire() and emscripten_lock_release() @also_with_minimal_runtime def test_wasm_worker_lock_waitinf(self): - self.btest(path_from_root('tests', 'wasm_worker', 'lock_waitinf_acquire.c'), expected='4000', args=['-sWASM_WORKERS']) + self.btest(test_file('wasm_worker/lock_waitinf_acquire.c'), expected='4000', args=['-sWASM_WORKERS']) # Tests emscripten_lock_wait_acquire() and emscripten_lock_try_acquire() in Worker. @also_with_minimal_runtime def test_wasm_worker_lock_wait(self): - self.btest(path_from_root('tests', 'wasm_worker', 'lock_wait_acquire.c'), expected='0', args=['-sWASM_WORKERS']) + self.btest(test_file('wasm_worker/lock_wait_acquire.c'), expected='0', args=['-sWASM_WORKERS']) # Tests emscripten_lock_wait_acquire() between two Wasm Workers. @also_with_minimal_runtime def test_wasm_worker_lock_wait2(self): - self.btest(path_from_root('tests', 'wasm_worker', 'lock_wait_acquire2.c'), expected='0', args=['-sWASM_WORKERS']) + self.btest(test_file('wasm_worker/lock_wait_acquire2.c'), expected='0', args=['-sWASM_WORKERS']) # Tests emscripten_lock_async_acquire() function. @also_with_minimal_runtime def test_wasm_worker_lock_async_acquire(self): - self.btest(path_from_root('tests', 'wasm_worker', 'lock_async_acquire.c'), expected='0', args=['-sWASM_WORKERS']) + self.btest(test_file('wasm_worker/lock_async_acquire.c'), expected='0', args=['-sWASM_WORKERS']) # Tests emscripten_lock_busyspin_wait_acquire() in Worker and main thread. @also_with_minimal_runtime def test_wasm_worker_lock_busyspin_wait(self): - self.btest(path_from_root('tests', 'wasm_worker', 'lock_busyspin_wait_acquire.c'), expected='0', args=['-sWASM_WORKERS']) + self.btest(test_file('wasm_worker/lock_busyspin_wait_acquire.c'), expected='0', args=['-sWASM_WORKERS']) # Tests emscripten_lock_busyspin_waitinf_acquire() in Worker and main thread. @also_with_minimal_runtime def test_wasm_worker_lock_busyspin_waitinf(self): - self.btest(path_from_root('tests', 'wasm_worker', 'lock_busyspin_waitinf_acquire.c'), expected='1', args=['-sWASM_WORKERS']) + self.btest(test_file('wasm_worker/lock_busyspin_waitinf_acquire.c'), expected='1', args=['-sWASM_WORKERS']) # Tests that proxied JS functions cannot be called from Wasm Workers @also_with_minimal_runtime def test_wasm_worker_no_proxied_js_functions(self): - self.btest(path_from_root('tests', 'wasm_worker', 'no_proxied_js_functions.c'), expected='0', - args=['--js-library', path_from_root('tests', 'wasm_worker', 'no_proxied_js_functions.js'), '-sWASM_WORKERS', '-sASSERTIONS']) + self.btest(test_file('wasm_worker/no_proxied_js_functions.c'), expected='0', + args=['--js-library', test_file('wasm_worker/no_proxied_js_functions.js'), '-sWASM_WORKERS', '-sASSERTIONS']) # Tests emscripten_semaphore_init(), emscripten_semaphore_waitinf_acquire() and emscripten_semaphore_release() @also_with_minimal_runtime def test_wasm_worker_semaphore_waitinf_acquire(self): - self.btest(path_from_root('tests', 'wasm_worker', 'semaphore_waitinf_acquire.c'), expected='0', args=['-sWASM_WORKERS']) + self.btest(test_file('wasm_worker/semaphore_waitinf_acquire.c'), expected='0', args=['-sWASM_WORKERS']) # Tests emscripten_semaphore_try_acquire() on the main thread @also_with_minimal_runtime def test_wasm_worker_semaphore_try_acquire(self): - self.btest(path_from_root('tests', 'wasm_worker', 'semaphore_try_acquire.c'), expected='0', args=['-sWASM_WORKERS']) + self.btest(test_file('wasm_worker/semaphore_try_acquire.c'), expected='0', args=['-sWASM_WORKERS']) @no_firefox('no 4GB support yet') @require_v8 diff --git a/tools/system_libs.py b/tools/system_libs.py index e76327e0f96c..ab50a52eada0 100644 --- a/tools/system_libs.py +++ b/tools/system_libs.py @@ -1041,6 +1041,8 @@ def get_cflags(self): '-D_DEBUG' if self.debug else '-Oz', '-DSTACK_OVERFLOW_CHECK=' + ('2' if self.stack_check else '0'), '-DWASM_WORKER_NO_TLS=' + ('0' if self.tls else '1')] + if not self.debug: + cflags += ['-DNDEBUG'] if self.is_ww or self.is_mt: cflags += ['-sWASM_WORKERS'] if settings.MAIN_MODULE: From b7ac44ddd6672ca06fa5c628c200888ca9aabf4a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jukka=20Jyl=C3=A4nki?= Date: Mon, 7 Mar 2022 11:32:20 +0200 Subject: [PATCH 62/65] Small code size optimization --- src/library_wasm_worker.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/library_wasm_worker.js b/src/library_wasm_worker.js index ba61dff90a1e..45d1445e5503 100644 --- a/src/library_wasm_worker.js +++ b/src/library_wasm_worker.js @@ -75,7 +75,7 @@ mergeInto(LibraryManager.library, { removeEventListener('message', __wasm_worker_appendToQueue); // ... then flush whatever messages we may have already gotten in the queue ... __wasm_worker_delayedMessageQueue.forEach(__wasm_worker_runPostMessage); - __wasm_worker_delayedMessageQueue = null; + __wasm_worker_delayedMessageQueue = 0; // ... and finally register the proper postMessage handler that immediately // dispatches incoming function calls without queueing them. addEventListener('message', __wasm_worker_runPostMessage); From 58399162080915f9e257d96844fc24830423ab28 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jukka=20Jyl=C3=A4nki?= Date: Mon, 7 Mar 2022 11:35:21 +0200 Subject: [PATCH 63/65] Small code size opt --- src/library_wasm_worker.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/library_wasm_worker.js b/src/library_wasm_worker.js index 45d1445e5503..8714a340fe84 100644 --- a/src/library_wasm_worker.js +++ b/src/library_wasm_worker.js @@ -73,9 +73,9 @@ mergeInto(LibraryManager.library, { // any postMessage function calls that have been received. Drop the temp // message handler that queued any pending incoming postMessage function calls ... removeEventListener('message', __wasm_worker_appendToQueue); - // ... then flush whatever messages we may have already gotten in the queue ... - __wasm_worker_delayedMessageQueue.forEach(__wasm_worker_runPostMessage); - __wasm_worker_delayedMessageQueue = 0; + // ... then flush whatever messages we may have already gotten in the queue, + // and clear __wasm_worker_delayedMessageQueue to undefined ... + __wasm_worker_delayedMessageQueue = __wasm_worker_delayedMessageQueue.forEach(__wasm_worker_runPostMessage); // ... and finally register the proper postMessage handler that immediately // dispatches incoming function calls without queueing them. addEventListener('message', __wasm_worker_runPostMessage); From a57099946e838456f1f37f88eca0884959135659 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jukka=20Jyl=C3=A4nki?= Date: Mon, 7 Mar 2022 11:40:09 +0200 Subject: [PATCH 64/65] Flake --- emcc.py | 1 - 1 file changed, 1 deletion(-) diff --git a/emcc.py b/emcc.py index 779d5191f93b..edafcfa9aec3 100755 --- a/emcc.py +++ b/emcc.py @@ -1439,7 +1439,6 @@ def phase_setup(options, state, newargs, user_settings): if '-mbulk-memory' not in newargs: newargs += ['-mbulk-memory'] - if 'DISABLE_EXCEPTION_CATCHING' in user_settings and 'EXCEPTION_CATCHING_ALLOWED' in user_settings: # If we get here then the user specified both DISABLE_EXCEPTION_CATCHING and EXCEPTION_CATCHING_ALLOWED # on the command line. This is no longer valid so report either an error or a warning (for From 8d53ac47658d1d33086ea899c8a043b6e9a0e1d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jukka=20Jyl=C3=A4nki?= Date: Mon, 7 Mar 2022 13:43:05 +0200 Subject: [PATCH 65/65] Update Wasm Workers code size test --- tests/code_size/hello_wasm_worker_wasm.js | 113 ++++++++------------ tests/code_size/hello_wasm_worker_wasm.json | 12 +-- tests/wasm_worker/wasm_worker_code_size.c | 6 +- 3 files changed, 54 insertions(+), 77 deletions(-) diff --git a/tests/code_size/hello_wasm_worker_wasm.js b/tests/code_size/hello_wasm_worker_wasm.js index 418d82ffbc2e..a412326192bc 100644 --- a/tests/code_size/hello_wasm_worker_wasm.js +++ b/tests/code_size/hello_wasm_worker_wasm.js @@ -1,102 +1,75 @@ -var e = Module; +var b = Module; -var f = e.$ww, g = new function(a) { - var c = new TextDecoder(a); - this.l = b => { - b.buffer instanceof SharedArrayBuffer && (b = new Uint8Array(b)); - return c.decode.call(c, b); - }; -}("utf8"); +var c = b.$ww; -function h(a) { - if (!a) return ""; - for (var c = a + NaN, b = a; !(b >= c) && k[b]; ) ++b; - return g.l(k.subarray(a, b)); -} +new function(a) { + new TextDecoder(a); +}("utf8"); -var l, k, m, n, p; +var e, f; -n = e.mem || new WebAssembly.Memory({ +e = b.mem || new WebAssembly.Memory({ initial: 256, maximum: 256, shared: !0 }); -var q = n.buffer; - -l = new Int32Array(q); - -k = new Uint8Array(q); - -new Uint16Array(q); +var g = e.buffer; -m = new Float64Array(q); +var h = {}, k = 1; -var r = { - 1908: function() { - console.log("Hello from wasm worker!"); - } -}, t = {}, u = 1; - -function v(a) { - w.push(a); +function l(a) { + m.push(a); } -function x(a) { +function n(a) { a = a.data; - let c = a._wsc; - c && p.get(c)(...a.x); + let d = a._wsc; + d && f.get(d)(...a.x); } -var w = [], y = []; +var m = []; -f && (t[0] = this, addEventListener("message", v)); +c && (h[0] = this, addEventListener("message", l)); -var z, A; +var p, q; -WebAssembly.instantiate(e.wasm, { +WebAssembly.instantiate(b.wasm, { a: { - b: function(a, c, b, d) { - throw "Assertion failed: " + h(a) + ",at: " + [ c ? h(c) : "unknown filename", b, d ? h(d) : "unknown function" ]; - }, - c: function(a, c, b) { - let d = t[u] = new Worker(e.$wb); - d.postMessage({ - $ww: u, - wasm: e.wasm, - js: e.js, - mem: n, + b: function(a, d, t) { + let r = h[k] = new Worker(b.$wb); + r.postMessage({ + $ww: k, + wasm: b.wasm, + js: b.js, + mem: e, sb: a, - sz: c, - tb: b + sz: d, + tb: t }); - d.addEventListener("message", x); - return u++; + r.addEventListener("message", n); + return k++; }, - f: function(a, c, b) { - y.length = 0; - var d; - for (b >>= 2; d = k[c++]; ) (d = 105 > d) && b & 1 && b++, y.push(d ? m[b++ >> 1] : l[b]), - ++b; - return r[a].apply(null, y); - }, - d: function() { + c: function() { return !1; }, - e: function(a, c) { - t[a].postMessage({ - _wsc: c, + d: function(a, d) { + h[a].postMessage({ + _wsc: d, x: [] }); }, - a: n + e: function() { + console.log("Hello from wasm worker!"); + }, + a: e } }).then((function(a) { a = a.instance.exports; - z = a.h; - A = a.j; - p = a.i; - f ? (a = e, A(a.sb, a.sz, a.tb), removeEventListener("message", v), w.forEach(x), - w = null, addEventListener("message", x)) : a.g(); - f || z(); + p = a.g; + q = a.i; + f = a.h; + c ? (a = b, q(a.sb, a.sz, a.tb), removeEventListener("message", l), m = m.forEach(n), + addEventListener("message", n)) : a.f(); + c || p(); })); \ No newline at end of file diff --git a/tests/code_size/hello_wasm_worker_wasm.json b/tests/code_size/hello_wasm_worker_wasm.json index 7f866cabd053..76112d47ad62 100644 --- a/tests/code_size/hello_wasm_worker_wasm.json +++ b/tests/code_size/hello_wasm_worker_wasm.json @@ -1,10 +1,10 @@ { "a.html": 737, "a.html.gz": 433, - "a.js": 1368, - "a.js.gz": 804, - "a.wasm": 2838, - "a.wasm.gz": 1476, - "total": 4943, - "total_gz": 2713 + "a.js": 813, + "a.js.gz": 501, + "a.wasm": 1791, + "a.wasm.gz": 991, + "total": 3341, + "total_gz": 1925 } diff --git a/tests/wasm_worker/wasm_worker_code_size.c b/tests/wasm_worker/wasm_worker_code_size.c index 2b41122d83b4..9930298614c6 100644 --- a/tests/wasm_worker/wasm_worker_code_size.c +++ b/tests/wasm_worker/wasm_worker_code_size.c @@ -3,9 +3,13 @@ // This file contains an absolute minimal sized Wasm Worker example, to keep a check on generated code size. +EM_JS(void, hello, (void), { + console.log("Hello from wasm worker!"); +}); + void run_in_worker() { - EM_ASM(console.log("Hello from wasm worker!")); + hello(); } int main()