Skip to content

Commit 516af5b

Browse files
committed
[jspi] Require async js functions when used with __async decorator.
The `_emval_await` library function is marked `_emval_await__async: true`, but the js function is not async. With memory64 enabled we auto convert to bigint and look for the async keyword (which is missing) to apply the await before creating the BigInt. With my changes __async will require an async js function, which signals the function is used with JSPI and the appropriate awaits are then inserted.
1 parent ac82bc4 commit 516af5b

File tree

10 files changed

+51
-21
lines changed

10 files changed

+51
-21
lines changed

src/jsifier.mjs

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99

1010
import assert from 'node:assert';
1111
import * as fs from 'node:fs/promises';
12+
import { isAsyncFunction } from 'node:util/types';
1213
import {
1314
ATMODULES,
1415
ATEXITS,
@@ -536,13 +537,13 @@ function(${args}) {
536537
deps.push('setTempRet0');
537538
}
538539

539-
let isAsyncFunction = false;
540+
let hasAsyncDecorator = false;
540541
if (ASYNCIFY) {
541542
const original = LibraryManager.library[symbol];
542543
if (typeof original == 'function') {
543-
isAsyncFunction = LibraryManager.library[symbol + '__async'];
544+
hasAsyncDecorator = LibraryManager.library[symbol + '__async'];
544545
}
545-
if (isAsyncFunction) {
546+
if (hasAsyncDecorator) {
546547
asyncFuncs.push(symbol);
547548
}
548549
}
@@ -669,6 +670,10 @@ function(${args}) {
669670
addImplicitDeps(snippet, deps);
670671
} else if (typeof snippet == 'function') {
671672
isFunction = true;
673+
if (ASYNCIFY == 2 && hasAsyncDecorator && !isAsyncFunction(snippet)) {
674+
error(`'${symbol}' is marked with the __async decorator but is not an async JS function.`);
675+
}
676+
672677
snippet = processLibraryFunction(snippet, symbol, mangled, deps, isStub);
673678
addImplicitDeps(snippet, deps);
674679
if (CHECK_DEPS && !isUserSymbol) {
@@ -766,7 +771,7 @@ function(${args}) {
766771
}
767772
contentText += `\n${mangled}.sig = '${sig}';`;
768773
}
769-
if (ASYNCIFY && isAsyncFunction) {
774+
if (ASYNCIFY && hasAsyncDecorator) {
770775
contentText += `\n${mangled}.isAsync = true;`;
771776
}
772777
if (isStub) {

src/lib/libasync.js

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -476,11 +476,11 @@ addToLibrary({
476476

477477
emscripten_sleep__deps: ['$safeSetTimeout'],
478478
emscripten_sleep__async: true,
479-
emscripten_sleep: (ms) => Asyncify.handleSleep((wakeUp) => safeSetTimeout(wakeUp, ms)),
479+
emscripten_sleep: async (ms) => Asyncify.handleSleep((wakeUp) => safeSetTimeout(wakeUp, ms)),
480480

481481
emscripten_wget_data__deps: ['$asyncLoad', 'malloc'],
482482
emscripten_wget_data__async: true,
483-
emscripten_wget_data: (url, pbuffer, pnum, perror) => Asyncify.handleAsync(async () => {
483+
emscripten_wget_data: async (url, pbuffer, pnum, perror) => Asyncify.handleAsync(async () => {
484484
/* no need for run dependency, this is async but will not do any prepare etc. step */
485485
try {
486486
const byteArray = await asyncLoad(UTF8ToString(url));
@@ -497,7 +497,7 @@ addToLibrary({
497497

498498
emscripten_scan_registers__deps: ['$safeSetTimeout'],
499499
emscripten_scan_registers__async: true,
500-
emscripten_scan_registers: (func) => {
500+
emscripten_scan_registers: async (func) => {
501501
return Asyncify.handleSleep((wakeUp) => {
502502
// We must first unwind, so things are spilled to the stack. Then while
503503
// we are pausing we do the actual scan. After that we can resume. Note
@@ -585,7 +585,7 @@ addToLibrary({
585585

586586
emscripten_fiber_swap__deps: ["$Asyncify", "$Fibers", '$stackSave'],
587587
emscripten_fiber_swap__async: true,
588-
emscripten_fiber_swap: (oldFiber, newFiber) => {
588+
emscripten_fiber_swap: async (oldFiber, newFiber) => {
589589
if (ABORT) return;
590590
#if ASYNCIFY_DEBUG
591591
dbg('ASYNCIFY/FIBER: swap', oldFiber, '->', newFiber, 'state:', Asyncify.state);

src/lib/libcore.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2615,7 +2615,7 @@ function wrapSyscallFunction(x, library, isWasi) {
26152615
post = handler + post;
26162616

26172617
if (pre || post) {
2618-
t = modifyJSFunction(t, (args, body) => `function (${args}) {\n${pre}${body}${post}}\n`);
2618+
t = modifyJSFunction(t, (args, body, async_) => `${async_} function (${args}) {\n${pre}${body}${post}}\n`);
26192619
}
26202620

26212621
library[x] = eval('(' + t + ')');

src/lib/libemval.js

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -402,12 +402,20 @@ ${functionBody}
402402
#if ASYNCIFY
403403
_emval_await__deps: ['$Emval', '$Asyncify'],
404404
_emval_await__async: true,
405+
#if ASYNCIFY == 1
405406
_emval_await: (promise) => {
406407
return Asyncify.handleAsync(async () => {
407408
var value = await Emval.toValue(promise);
408409
return Emval.toHandle(value);
409410
});
410411
},
412+
#endif
413+
#if ASYNCIFY == 2
414+
_emval_await: async (promise) => {
415+
var value = await Emval.toValue(promise);
416+
return Emval.toHandle(value);
417+
},
418+
#endif
411419
#endif
412420

413421
_emval_iter_begin__deps: ['$Emval'],

src/lib/libidbstore.js

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@ var LibraryIDBStore = {
9494
#if ASYNCIFY
9595
emscripten_idb_load__async: true,
9696
emscripten_idb_load__deps: ['malloc'],
97-
emscripten_idb_load: (db, id, pbuffer, pnum, perror) => Asyncify.handleSleep((wakeUp) => {
97+
emscripten_idb_load: async (db, id, pbuffer, pnum, perror) => Asyncify.handleSleep((wakeUp) => {
9898
IDBStore.getFile(UTF8ToString(db), UTF8ToString(id), (error, byteArray) => {
9999
if (error) {
100100
{{{ makeSetValue('perror', 0, '1', 'i32') }}};
@@ -110,7 +110,7 @@ var LibraryIDBStore = {
110110
});
111111
}),
112112
emscripten_idb_store__async: true,
113-
emscripten_idb_store: (db, id, ptr, num, perror) => Asyncify.handleSleep((wakeUp) => {
113+
emscripten_idb_store: async (db, id, ptr, num, perror) => Asyncify.handleSleep((wakeUp) => {
114114
IDBStore.setFile(UTF8ToString(db), UTF8ToString(id), new Uint8Array(HEAPU8.subarray(ptr, ptr+num)), (error) => {
115115
// Closure warns about storing booleans in TypedArrays.
116116
/** @suppress{checkTypes} */
@@ -119,15 +119,15 @@ var LibraryIDBStore = {
119119
});
120120
}),
121121
emscripten_idb_delete__async: true,
122-
emscripten_idb_delete: (db, id, perror) => Asyncify.handleSleep((wakeUp) => {
122+
emscripten_idb_delete: async (db, id, perror) => Asyncify.handleSleep((wakeUp) => {
123123
IDBStore.deleteFile(UTF8ToString(db), UTF8ToString(id), (error) => {
124124
/** @suppress{checkTypes} */
125125
{{{ makeSetValue('perror', 0, '!!error', 'i32') }}};
126126
wakeUp();
127127
});
128128
}),
129129
emscripten_idb_exists__async: true,
130-
emscripten_idb_exists: (db, id, pexists, perror) => Asyncify.handleSleep((wakeUp) => {
130+
emscripten_idb_exists: async (db, id, pexists, perror) => Asyncify.handleSleep((wakeUp) => {
131131
IDBStore.existsFile(UTF8ToString(db), UTF8ToString(id), (error, exists) => {
132132
/** @suppress{checkTypes} */
133133
{{{ makeSetValue('pexists', 0, '!!exists', 'i32') }}};
@@ -137,7 +137,7 @@ var LibraryIDBStore = {
137137
});
138138
}),
139139
emscripten_idb_clear__async: true,
140-
emscripten_idb_clear: (db, perror) => Asyncify.handleSleep((wakeUp) => {
140+
emscripten_idb_clear: async (db, perror) => Asyncify.handleSleep((wakeUp) => {
141141
IDBStore.clearStore(UTF8ToString(db), (error) => {
142142
/** @suppress{checkTypes} */
143143
{{{ makeSetValue('perror', 0, '!!error', 'i32') }}};
@@ -146,7 +146,7 @@ var LibraryIDBStore = {
146146
}),
147147
// extra worker methods - proxied
148148
emscripten_idb_load_blob__async: true,
149-
emscripten_idb_load_blob: (db, id, pblob, perror) => Asyncify.handleSleep((wakeUp) => {
149+
emscripten_idb_load_blob: async (db, id, pblob, perror) => Asyncify.handleSleep((wakeUp) => {
150150
#if ASSERTIONS
151151
assert(!IDBStore.pending);
152152
#endif
@@ -174,7 +174,7 @@ var LibraryIDBStore = {
174174
});
175175
}),
176176
emscripten_idb_store_blob__async: true,
177-
emscripten_idb_store_blob: (db, id, ptr, num, perror) => Asyncify.handleSleep((wakeUp) => {
177+
emscripten_idb_store_blob: async (db, id, ptr, num, perror) => Asyncify.handleSleep((wakeUp) => {
178178
#if ASSERTIONS
179179
assert(!IDBStore.pending);
180180
#endif

src/lib/libpromise.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -261,7 +261,7 @@ addToLibrary({
261261
#if ASYNCIFY
262262
emscripten_promise_await__deps: ['$getPromise', '$setPromiseResult'],
263263
#endif
264-
emscripten_promise_await: (returnValuePtr, id) => {
264+
emscripten_promise_await: async (returnValuePtr, id) => {
265265
#if ASYNCIFY
266266
#if RUNTIME_DEBUG
267267
dbg(`emscripten_promise_await: ${id}`);

src/lib/libsdl.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1753,7 +1753,7 @@ var LibrarySDL = {
17531753
#else
17541754
SDL_Delay__deps: ['emscripten_sleep'],
17551755
SDL_Delay__async: true,
1756-
SDL_Delay: (delay) => _emscripten_sleep(delay),
1756+
SDL_Delay: async (delay) => _emscripten_sleep(delay),
17571757
#endif
17581758

17591759
SDL_WM_SetCaption__proxy: 'sync',

src/lib/libwasi.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -532,7 +532,7 @@ var WasiLibrary = {
532532
return 0;
533533
},
534534

535-
fd_sync: (fd) => {
535+
fd_sync: {{{ asyncIf(ASYNCIFY) }}} (fd) => {
536536
#if SYSCALLS_REQUIRE_FILESYSTEM
537537
var stream = SYSCALLS.getStreamFromFD(fd);
538538
#if ASYNCIFY

test/test_browser.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4960,8 +4960,6 @@ def test_embind_with_pthreads(self):
49604960
def test_embind(self, args):
49614961
if is_jspi(args) and not is_chrome():
49624962
self.skipTest(f'Current browser ({common.EMTEST_BROWSER}) does not support JSPI. Only chromium-based browsers ({CHROMIUM_BASED_BROWSERS}) support JSPI today.')
4963-
if is_jspi(args) and self.is_wasm64():
4964-
self.skipTest('_emval_await fails')
49654963

49664964
self.btest('embind_with_asyncify.cpp', '1', cflags=['-lembind'] + args)
49674965

test/test_other.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3246,6 +3246,7 @@ def test_embind_asyncify(self):
32463246
'': [['-sDYNAMIC_EXECUTION=1']],
32473247
'no_dynamic': [['-sDYNAMIC_EXECUTION=0']],
32483248
'dyncall': [['-sALLOW_MEMORY_GROWTH', '-sMAXIMUM_MEMORY=4GB']],
3249+
'wasm64': (['-sMEMORY64'],),
32493250
})
32503251
@requires_jspi
32513252
def test_embind_jspi(self, args):
@@ -3484,6 +3485,24 @@ def test_jspi_async_function(self):
34843485
'-Wno-experimental',
34853486
'--post-js=post.js'])
34863487

3488+
@requires_jspi
3489+
def test_jspi_bad_library_function(self):
3490+
create_file('lib.js', r'''
3491+
addToLibrary({
3492+
foo__async: true,
3493+
foo: function(f) {},
3494+
});
3495+
''')
3496+
create_file('main.c', r'''
3497+
#include <emscripten.h>
3498+
extern void foo();
3499+
EMSCRIPTEN_KEEPALIVE void test() {
3500+
foo();
3501+
}
3502+
''')
3503+
err = self.expect_fail([EMCC, 'main.c', '-o', 'out.js', '-sJSPI', '--js-library=lib.js', '-Wno-experimental',])
3504+
self.assertContained('error: foo is marked with the __async decorator but is not an async JS function.', err)
3505+
34873506
@requires_dev_dependency('typescript')
34883507
@parameterized({
34893508
'commonjs': [['-sMODULARIZE'], ['--module', 'commonjs', '--moduleResolution', 'node']],

0 commit comments

Comments
 (0)