diff --git a/src/jsifier.mjs b/src/jsifier.mjs index 6d9f25560f09a..f9738f9e88948 100644 --- a/src/jsifier.mjs +++ b/src/jsifier.mjs @@ -9,6 +9,7 @@ import assert from 'node:assert'; import * as fs from 'node:fs/promises'; +import { isAsyncFunction } from 'node:util/types'; import { ATMODULES, ATEXITS, @@ -337,7 +338,7 @@ return ${makeReturn64(await_ + body)}; return `\ ${async_}function(${args}) { ${argConversions} -var ret = (() => { ${body} })(); +var ret = (${async_}() => { ${body} })(); return ${makeReturn64(await_ + 'ret')}; }`; } @@ -540,13 +541,13 @@ function(${args}) { deps.push('setTempRet0'); } - let isAsyncFunction = false; + let hasAsyncDecorator = false; if (ASYNCIFY) { const original = LibraryManager.library[symbol]; if (typeof original == 'function') { - isAsyncFunction = LibraryManager.library[symbol + '__async']; + hasAsyncDecorator = LibraryManager.library[symbol + '__async']; } - if (isAsyncFunction) { + if (hasAsyncDecorator) { asyncFuncs.push(symbol); } } @@ -681,6 +682,10 @@ function(${args}) { snippet = stringifyWithFunctions(snippet); addImplicitDeps(snippet, deps); } else if (isFunction) { + if (ASYNCIFY == 2 && hasAsyncDecorator && !isAsyncFunction(snippet)) { + error(`'${symbol}' is marked with the __async decorator but is not an async JS function.`); + } + snippet = processLibraryFunction(snippet, symbol, mangled, deps, isStub); addImplicitDeps(snippet, deps); if (CHECK_DEPS && !isUserSymbol) { @@ -775,7 +780,7 @@ function(${args}) { } contentText += `\n${mangled}.sig = '${sig}';`; } - if (ASYNCIFY && isAsyncFunction) { + if (ASYNCIFY && hasAsyncDecorator) { contentText += `\n${mangled}.isAsync = true;`; } if (isStub) { diff --git a/src/lib/libasync.js b/src/lib/libasync.js index c79e4f6bfccbf..3eb5f288f5b9f 100644 --- a/src/lib/libasync.js +++ b/src/lib/libasync.js @@ -476,11 +476,11 @@ addToLibrary({ emscripten_sleep__deps: ['$safeSetTimeout'], emscripten_sleep__async: true, - emscripten_sleep: (ms) => Asyncify.handleSleep((wakeUp) => safeSetTimeout(wakeUp, ms)), + emscripten_sleep: async (ms) => Asyncify.handleSleep((wakeUp) => safeSetTimeout(wakeUp, ms)), emscripten_wget_data__deps: ['$asyncLoad', 'malloc'], emscripten_wget_data__async: true, - emscripten_wget_data: (url, pbuffer, pnum, perror) => Asyncify.handleAsync(async () => { + emscripten_wget_data: async (url, pbuffer, pnum, perror) => Asyncify.handleAsync(async () => { /* no need for run dependency, this is async but will not do any prepare etc. step */ try { const byteArray = await asyncLoad(UTF8ToString(url)); @@ -497,7 +497,7 @@ addToLibrary({ emscripten_scan_registers__deps: ['$safeSetTimeout'], emscripten_scan_registers__async: true, - emscripten_scan_registers: (func) => { + emscripten_scan_registers: async (func) => { return Asyncify.handleSleep((wakeUp) => { // We must first unwind, so things are spilled to the stack. Then while // we are pausing we do the actual scan. After that we can resume. Note @@ -585,7 +585,7 @@ addToLibrary({ emscripten_fiber_swap__deps: ["$Asyncify", "$Fibers", '$stackSave'], emscripten_fiber_swap__async: true, - emscripten_fiber_swap: (oldFiber, newFiber) => { + emscripten_fiber_swap: async (oldFiber, newFiber) => { if (ABORT) return; #if ASYNCIFY_DEBUG dbg('ASYNCIFY/FIBER: swap', oldFiber, '->', newFiber, 'state:', Asyncify.state); diff --git a/src/lib/libcore.js b/src/lib/libcore.js index 9c2f828c43920..8fa000cf2eb03 100644 --- a/src/lib/libcore.js +++ b/src/lib/libcore.js @@ -2622,7 +2622,7 @@ function wrapSyscallFunction(x, library, isWasi) { post = handler + post; if (pre || post) { - t = modifyJSFunction(t, (args, body) => `function (${args}) {\n${pre}${body}${post}}\n`); + t = modifyJSFunction(t, (args, body, async_) => `${async_} function (${args}) {\n${pre}${body}${post}}\n`); } library[x] = eval('(' + t + ')'); diff --git a/src/lib/libemval.js b/src/lib/libemval.js index e9e8553cd49d4..708f8130949a7 100644 --- a/src/lib/libemval.js +++ b/src/lib/libemval.js @@ -402,12 +402,19 @@ ${functionBody} #if ASYNCIFY _emval_await__deps: ['$Emval', '$Asyncify'], _emval_await__async: true, +#if ASYNCIFY == 1 _emval_await: (promise) => { return Asyncify.handleAsync(async () => { var value = await Emval.toValue(promise); return Emval.toHandle(value); }); }, +#else + _emval_await: async (promise) => { + var value = await Emval.toValue(promise); + return Emval.toHandle(value); + }, +#endif #endif _emval_iter_begin__deps: ['$Emval'], diff --git a/src/lib/libidbstore.js b/src/lib/libidbstore.js index 224d1d1d434a8..a66809290f7c0 100644 --- a/src/lib/libidbstore.js +++ b/src/lib/libidbstore.js @@ -94,7 +94,7 @@ var LibraryIDBStore = { #if ASYNCIFY emscripten_idb_load__async: true, emscripten_idb_load__deps: ['malloc'], - emscripten_idb_load: (db, id, pbuffer, pnum, perror) => Asyncify.handleSleep((wakeUp) => { + emscripten_idb_load: async (db, id, pbuffer, pnum, perror) => Asyncify.handleSleep((wakeUp) => { IDBStore.getFile(UTF8ToString(db), UTF8ToString(id), (error, byteArray) => { if (error) { {{{ makeSetValue('perror', 0, '1', 'i32') }}}; @@ -110,7 +110,7 @@ var LibraryIDBStore = { }); }), emscripten_idb_store__async: true, - emscripten_idb_store: (db, id, ptr, num, perror) => Asyncify.handleSleep((wakeUp) => { + emscripten_idb_store: async (db, id, ptr, num, perror) => Asyncify.handleSleep((wakeUp) => { IDBStore.setFile(UTF8ToString(db), UTF8ToString(id), new Uint8Array(HEAPU8.subarray(ptr, ptr+num)), (error) => { // Closure warns about storing booleans in TypedArrays. /** @suppress{checkTypes} */ @@ -119,7 +119,7 @@ var LibraryIDBStore = { }); }), emscripten_idb_delete__async: true, - emscripten_idb_delete: (db, id, perror) => Asyncify.handleSleep((wakeUp) => { + emscripten_idb_delete: async (db, id, perror) => Asyncify.handleSleep((wakeUp) => { IDBStore.deleteFile(UTF8ToString(db), UTF8ToString(id), (error) => { /** @suppress{checkTypes} */ {{{ makeSetValue('perror', 0, '!!error', 'i32') }}}; @@ -127,7 +127,7 @@ var LibraryIDBStore = { }); }), emscripten_idb_exists__async: true, - emscripten_idb_exists: (db, id, pexists, perror) => Asyncify.handleSleep((wakeUp) => { + emscripten_idb_exists: async (db, id, pexists, perror) => Asyncify.handleSleep((wakeUp) => { IDBStore.existsFile(UTF8ToString(db), UTF8ToString(id), (error, exists) => { /** @suppress{checkTypes} */ {{{ makeSetValue('pexists', 0, '!!exists', 'i32') }}}; @@ -137,7 +137,7 @@ var LibraryIDBStore = { }); }), emscripten_idb_clear__async: true, - emscripten_idb_clear: (db, perror) => Asyncify.handleSleep((wakeUp) => { + emscripten_idb_clear: async (db, perror) => Asyncify.handleSleep((wakeUp) => { IDBStore.clearStore(UTF8ToString(db), (error) => { /** @suppress{checkTypes} */ {{{ makeSetValue('perror', 0, '!!error', 'i32') }}}; @@ -146,7 +146,7 @@ var LibraryIDBStore = { }), // extra worker methods - proxied emscripten_idb_load_blob__async: true, - emscripten_idb_load_blob: (db, id, pblob, perror) => Asyncify.handleSleep((wakeUp) => { + emscripten_idb_load_blob: async (db, id, pblob, perror) => Asyncify.handleSleep((wakeUp) => { #if ASSERTIONS assert(!IDBStore.pending); #endif @@ -174,7 +174,7 @@ var LibraryIDBStore = { }); }), emscripten_idb_store_blob__async: true, - emscripten_idb_store_blob: (db, id, ptr, num, perror) => Asyncify.handleSleep((wakeUp) => { + emscripten_idb_store_blob: async (db, id, ptr, num, perror) => Asyncify.handleSleep((wakeUp) => { #if ASSERTIONS assert(!IDBStore.pending); #endif diff --git a/src/lib/libpromise.js b/src/lib/libpromise.js index db4838e09cc22..0be6872d09c52 100644 --- a/src/lib/libpromise.js +++ b/src/lib/libpromise.js @@ -261,7 +261,7 @@ addToLibrary({ #if ASYNCIFY emscripten_promise_await__deps: ['$getPromise', '$setPromiseResult'], #endif - emscripten_promise_await: (returnValuePtr, id) => { + emscripten_promise_await: async (returnValuePtr, id) => { #if ASYNCIFY #if RUNTIME_DEBUG dbg(`emscripten_promise_await: ${id}`); diff --git a/src/lib/libsdl.js b/src/lib/libsdl.js index c810a2c5e0302..f380329b0dec8 100644 --- a/src/lib/libsdl.js +++ b/src/lib/libsdl.js @@ -1746,7 +1746,7 @@ var LibrarySDL = { #if ASYNCIFY SDL_Delay__deps: ['emscripten_sleep'], SDL_Delay__async: true, - SDL_Delay: (delay) => _emscripten_sleep(delay), + SDL_Delay: async (delay) => _emscripten_sleep(delay), #else SDL_Delay: (delay) => { if (!ENVIRONMENT_IS_WORKER) abort('SDL_Delay called on the main thread! Potential infinite loop, quitting. (consider building with async support like ASYNCIFY)'); diff --git a/src/lib/libwasi.js b/src/lib/libwasi.js index 2d3b143f0969a..316a189ba9c38 100644 --- a/src/lib/libwasi.js +++ b/src/lib/libwasi.js @@ -532,7 +532,7 @@ var WasiLibrary = { return 0; }, - fd_sync: (fd) => { + fd_sync: {{{ asyncIf(ASYNCIFY) }}} (fd) => { #if SYSCALLS_REQUIRE_FILESYSTEM var stream = SYSCALLS.getStreamFromFD(fd); #if ASYNCIFY diff --git a/test/codesize/test_codesize_hello_dylink_all.json b/test/codesize/test_codesize_hello_dylink_all.json index f07e4dfa5ee9d..346c9fb9c43b9 100644 --- a/test/codesize/test_codesize_hello_dylink_all.json +++ b/test/codesize/test_codesize_hello_dylink_all.json @@ -1,7 +1,7 @@ { - "a.out.js": 245841, + "a.out.js": 245846, "a.out.nodebug.wasm": 597746, - "total": 843587, + "total": 843592, "sent": [ "IMG_Init", "IMG_Load", diff --git a/test/test_browser.py b/test/test_browser.py index 7b8a868ada5f6..923c86b883053 100644 --- a/test/test_browser.py +++ b/test/test_browser.py @@ -4982,8 +4982,6 @@ def test_embind_with_pthreads(self): def test_embind(self, args): if is_jspi(args) and not is_chrome(): self.skipTest(f'Current browser ({common.EMTEST_BROWSER}) does not support JSPI. Only chromium-based browsers ({CHROMIUM_BASED_BROWSERS}) support JSPI today.') - if is_jspi(args) and self.is_wasm64(): - self.skipTest('_emval_await fails') self.btest('embind_with_asyncify.cpp', '1', cflags=['-lembind'] + args) diff --git a/test/test_other.py b/test/test_other.py index 8caf035a2f2b6..cb1afa722a2f4 100644 --- a/test/test_other.py +++ b/test/test_other.py @@ -3242,10 +3242,11 @@ def test_embind_asyncify(self): ''') self.do_runf('main.cpp', 'done', cflags=['-lembind', '-sASYNCIFY', '--post-js', 'post.js']) + @also_with_wasm64 @parameterized({ - '': [['-sDYNAMIC_EXECUTION=1']], - 'no_dynamic': [['-sDYNAMIC_EXECUTION=0']], - 'dyncall': [['-sALLOW_MEMORY_GROWTH', '-sMAXIMUM_MEMORY=4GB']], + '': (['-sDYNAMIC_EXECUTION=1'],), + 'no_dynamic': (['-sDYNAMIC_EXECUTION=0'],), + 'dyncall': (['-sALLOW_MEMORY_GROWTH', '-sMAXIMUM_MEMORY=4GB'],), }) @requires_jspi def test_embind_jspi(self, args): @@ -5012,6 +5013,24 @@ def test_jslib_system_lib_name(self): ''') self.do_runf('src.c', 'jslibfunc: 12', cflags=['--js-library', 'libcore.js']) + @requires_jspi + def test_jslib_jspi_missing_async(self): + create_file('lib.js', r''' + addToLibrary({ + foo__async: true, + foo: function(f) {}, + }); + ''') + create_file('main.c', r''' + #include + extern void foo(); + EMSCRIPTEN_KEEPALIVE void test() { + foo(); + } + ''') + err = self.expect_fail([EMCC, 'main.c', '-o', 'out.js', '-sJSPI', '--js-library=lib.js', '-Wno-experimental']) + self.assertContained('error: \'foo\' is marked with the __async decorator but is not an async JS function.', err) + def test_EMCC_BUILD_DIR(self): # EMCC_BUILD_DIR was necessary in the past since we used to force the cwd to be src/ for # technical reasons.