From 7309c656face8b2b089f50a2eee2ad3970838b3f Mon Sep 17 00:00:00 2001 From: Sam Clegg Date: Tue, 30 Dec 2025 07:46:23 -0800 Subject: [PATCH] Add `__async: auto` decorator This decorator automatically generates the Asyncify.handleAsync wrapper code. Hopefully we can transition all out `__async` functions soon and just make this the default. --- src/jsifier.mjs | 18 ++++++++++++++++++ src/lib/libasync.js | 6 +++--- src/lib/libidbstore.js | 32 ++++++++++++++++---------------- src/lib/libpromise.js | 6 +++--- src/lib/libwasi.js | 8 ++++---- src/utility.mjs | 4 ++-- test/test_jslib.py | 4 ++-- 7 files changed, 48 insertions(+), 30 deletions(-) diff --git a/src/jsifier.mjs b/src/jsifier.mjs index 0d9f9aeddffa6..45043577017fb 100644 --- a/src/jsifier.mjs +++ b/src/jsifier.mjs @@ -350,6 +350,19 @@ ${body}; }); } +function handleAsyncFunction(snippet) { + return modifyJSFunction(snippet, (args, body, async_, oneliner) => { + if (!oneliner) { + body = `{\n${body}\n}`; + } + return `\ +${async_}function(${args}) { + let innerFunc = ${async_} () => ${body}; + return Asyncify.handleAsync(innerFunc); +}\n`; + }); +} + export async function runJSify(outputFile, symbolsOnly) { const libraryItems = []; const symbolDeps = {}; @@ -440,6 +453,11 @@ function(${args}) { compileTimeContext.i53ConversionDeps.forEach((d) => deps.push(d)); } + const isAsyncFunction = LibraryManager.library[symbol + '__async']; + if (ASYNCIFY && isAsyncFunction == 'auto') { + snippet = handleAsyncFunction(snippet); + } + const proxyingMode = LibraryManager.library[symbol + '__proxy']; if (proxyingMode) { if (!['sync', 'async', 'none'].includes(proxyingMode)) { diff --git a/src/lib/libasync.js b/src/lib/libasync.js index 096386a14daad..71893b6f19e78 100644 --- a/src/lib/libasync.js +++ b/src/lib/libasync.js @@ -485,8 +485,8 @@ addToLibrary({ emscripten_sleep: (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: 'auto', + emscripten_wget_data: async (url, pbuffer, pnum, perror) => { /* no need for run dependency, this is async but will not do any prepare etc. step */ try { const byteArray = await asyncLoad(UTF8ToString(url)); @@ -499,7 +499,7 @@ addToLibrary({ } catch (err) { {{{ makeSetValue('perror', 0, '1', 'i32') }}}; } - }), + }, emscripten_scan_registers__deps: ['$safeSetTimeout'], emscripten_scan_registers__async: true, diff --git a/src/lib/libidbstore.js b/src/lib/libidbstore.js index 3ecd106b4d771..b2a9f058e5b13 100644 --- a/src/lib/libidbstore.js +++ b/src/lib/libidbstore.js @@ -92,13 +92,13 @@ var LibraryIDBStore = { }, #if ASYNCIFY - emscripten_idb_load__async: true, + emscripten_idb_load__async: 'auto', emscripten_idb_load__deps: ['malloc'], - emscripten_idb_load: (db, id, pbuffer, pnum, perror) => Asyncify.handleSleep((wakeUp) => { + emscripten_idb_load: (db, id, pbuffer, pnum, perror) => new Promise((resolve) => { IDBStore.getFile(UTF8ToString(db), UTF8ToString(id), (error, byteArray) => { if (error) { {{{ makeSetValue('perror', 0, '1', 'i32') }}}; - wakeUp(); + resolve(); return; } var buffer = _malloc(byteArray.length); // must be freed by the caller! @@ -106,42 +106,42 @@ var LibraryIDBStore = { {{{ makeSetValue('pbuffer', 0, 'buffer', '*') }}}; {{{ makeSetValue('pnum', 0, 'byteArray.length', 'i32') }}}; {{{ makeSetValue('perror', 0, '0', 'i32') }}}; - wakeUp(); + resolve(); }); }), - emscripten_idb_store__async: true, - emscripten_idb_store: (db, id, ptr, num, perror) => Asyncify.handleSleep((wakeUp) => { + emscripten_idb_store__async: 'auto', + emscripten_idb_store: (db, id, ptr, num, perror) => new Promise((resolve) => { IDBStore.setFile(UTF8ToString(db), UTF8ToString(id), new Uint8Array(HEAPU8.subarray(ptr, ptr+num)), (error) => { // Closure warns about storing booleans in TypedArrays. /** @suppress{checkTypes} */ {{{ makeSetValue('perror', 0, '!!error', 'i32') }}}; - wakeUp(); + resolve(); }); }), - emscripten_idb_delete__async: true, - emscripten_idb_delete: (db, id, perror) => Asyncify.handleSleep((wakeUp) => { + emscripten_idb_delete__async: 'auto', + emscripten_idb_delete: (db, id, perror) => new Promise((resolve) => { IDBStore.deleteFile(UTF8ToString(db), UTF8ToString(id), (error) => { /** @suppress{checkTypes} */ {{{ makeSetValue('perror', 0, '!!error', 'i32') }}}; - wakeUp(); + resolve(); }); }), - emscripten_idb_exists__async: true, - emscripten_idb_exists: (db, id, pexists, perror) => Asyncify.handleSleep((wakeUp) => { + emscripten_idb_exists__async: 'auto', + emscripten_idb_exists: (db, id, pexists, perror) => new Promise((resolve) => { IDBStore.existsFile(UTF8ToString(db), UTF8ToString(id), (error, exists) => { /** @suppress{checkTypes} */ {{{ makeSetValue('pexists', 0, '!!exists', 'i32') }}}; /** @suppress{checkTypes} */ {{{ makeSetValue('perror', 0, '!!error', 'i32') }}}; - wakeUp(); + resolve(); }); }), - emscripten_idb_clear__async: true, - emscripten_idb_clear: (db, perror) => Asyncify.handleSleep((wakeUp) => { + emscripten_idb_clear__async: 'auto', + emscripten_idb_clear: (db, perror) => new Promise((resolve) => { IDBStore.clearStore(UTF8ToString(db), (error) => { /** @suppress{checkTypes} */ {{{ makeSetValue('perror', 0, '!!error', 'i32') }}}; - wakeUp(); + resolve(); }); }), #else diff --git a/src/lib/libpromise.js b/src/lib/libpromise.js index db4838e09cc22..0ea127ce84c48 100644 --- a/src/lib/libpromise.js +++ b/src/lib/libpromise.js @@ -257,7 +257,7 @@ addToLibrary({ return id; }, - emscripten_promise_await__async: true, + emscripten_promise_await__async: 'auto', #if ASYNCIFY emscripten_promise_await__deps: ['$getPromise', '$setPromiseResult'], #endif @@ -266,10 +266,10 @@ addToLibrary({ #if RUNTIME_DEBUG dbg(`emscripten_promise_await: ${id}`); #endif - return Asyncify.handleAsync(() => getPromise(id).then( + return getPromise(id).then( value => setPromiseResult(returnValuePtr, true, value), error => setPromiseResult(returnValuePtr, false, error) - )); + ); #else abort('emscripten_promise_await is only available with ASYNCIFY'); #endif diff --git a/src/lib/libwasi.js b/src/lib/libwasi.js index 651fb52e3847c..974ce27724d0f 100644 --- a/src/lib/libwasi.js +++ b/src/lib/libwasi.js @@ -540,16 +540,16 @@ var WasiLibrary = { return 0; }, - fd_sync__async: true, - fd_sync: (fd) => { + fd_sync__async: 'auto', + fd_sync: {{{ asyncIf(ASYNCIFY) }}} (fd) => { #if SYSCALLS_REQUIRE_FILESYSTEM var stream = SYSCALLS.getStreamFromFD(fd); var rtn = stream.stream_ops?.fsync?.(stream); #if ASYNCIFY var mount = stream.node.mount; if (mount.type.syncfs) { - return Asyncify.handleSleep((wakeUp) => { - mount.type.syncfs(mount, false, (err) => wakeUp(err ? {{{ cDefs.EIO }}} : 0)); + return new Promise((resolve) => { + mount.type.syncfs(mount, false, (err) => resolve(err ? {{{ cDefs.EIO }}} : 0)); }); } #endif // ASYNCIFY diff --git a/src/utility.mjs b/src/utility.mjs index d9123a12be99b..c7f6008ff3082 100644 --- a/src/utility.mjs +++ b/src/utility.mjs @@ -186,12 +186,12 @@ export function mergeInto(obj, other, options = null) { __noleakcheck: 'boolean', __internal: 'boolean', __user: 'boolean', - __async: 'boolean', + __async: ['string', 'boolean'], __i53abi: 'boolean', }; const expected = decoratorTypes[decoratorName]; if (type !== expected && !expected.includes(type)) { - error(`Decorator (${key}} has wrong type. Expected '${expected}' not '${type}'`); + error(`Decorator (${key}) has wrong type. Expected '${expected}' not '${type}'`); } } } diff --git a/test/test_jslib.py b/test/test_jslib.py index a2d8b063c9bb8..dbdf74773d24f 100644 --- a/test/test_jslib.py +++ b/test/test_jslib.py @@ -277,12 +277,12 @@ def test_jslib_invalid_deps(self): def test_jslib_invalid_decorator(self): create_file('lib.js', r''' addToLibrary({ - jslibfunc__async: 'hello', + jslibfunc__internal: 'hello', jslibfunc: (x) => {}, }); ''') self.assert_fail([EMCC, test_file('hello_world.c'), '--js-library', 'lib.js'], - "lib.js: Decorator (jslibfunc__async} has wrong type. Expected 'boolean' not 'string'") + "lib.js: Decorator (jslibfunc__internal) has wrong type. Expected 'boolean' not 'string'") @also_with_wasm64 @also_without_bigint