Skip to content

Commit be10d98

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 4967c54 commit be10d98

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,
@@ -540,13 +541,13 @@ function(${args}) {
540541
deps.push('setTempRet0');
541542
}
542543

543-
let isAsyncFunction = false;
544+
let hasAsyncDecorator = false;
544545
if (ASYNCIFY) {
545546
const original = LibraryManager.library[symbol];
546547
if (typeof original == 'function') {
547-
isAsyncFunction = LibraryManager.library[symbol + '__async'];
548+
hasAsyncDecorator = LibraryManager.library[symbol + '__async'];
548549
}
549-
if (isAsyncFunction) {
550+
if (hasAsyncDecorator) {
550551
asyncFuncs.push(symbol);
551552
}
552553
}
@@ -676,6 +677,10 @@ function(${args}) {
676677
snippet = stringifyWithFunctions(snippet);
677678
addImplicitDeps(snippet, deps);
678679
} else if (isFunction) {
680+
if (ASYNCIFY == 2 && hasAsyncDecorator && !isAsyncFunction(snippet)) {
681+
error(`'${symbol}' is marked with the __async decorator but is not an async JS function.`);
682+
}
683+
679684
snippet = processLibraryFunction(snippet, symbol, mangled, deps, isStub);
680685
addImplicitDeps(snippet, deps);
681686
if (CHECK_DEPS && !isUserSymbol) {
@@ -770,7 +775,7 @@ function(${args}) {
770775
}
771776
contentText += `\n${mangled}.sig = '${sig}';`;
772777
}
773-
if (ASYNCIFY && isAsyncFunction) {
778+
if (ASYNCIFY && hasAsyncDecorator) {
774779
contentText += `\n${mangled}.isAsync = true;`;
775780
}
776781
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
@@ -2619,7 +2619,7 @@ function wrapSyscallFunction(x, library, isWasi) {
26192619
post = handler + post;
26202620

26212621
if (pre || post) {
2622-
t = modifyJSFunction(t, (args, body) => `function (${args}) {\n${pre}${body}${post}}\n`);
2622+
t = modifyJSFunction(t, (args, body, async_) => `${async_} function (${args}) {\n${pre}${body}${post}}\n`);
26232623
}
26242624

26252625
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
@@ -1746,7 +1746,7 @@ var LibrarySDL = {
17461746
#if ASYNCIFY
17471747
SDL_Delay__deps: ['emscripten_sleep'],
17481748
SDL_Delay__async: true,
1749-
SDL_Delay: (delay) => _emscripten_sleep(delay),
1749+
SDL_Delay: async (delay) => _emscripten_sleep(delay),
17501750
#else
17511751
SDL_Delay: (delay) => {
17521752
if (!ENVIRONMENT_IS_WORKER) abort('SDL_Delay called on the main thread! Potential infinite loop, quitting. (consider building with async support like ASYNCIFY)');

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
@@ -5026,8 +5026,6 @@ def test_embind_with_pthreads(self):
50265026
def test_embind(self, args):
50275027
if is_jspi(args) and not is_chrome():
50285028
self.skipTest(f'Current browser ({common.EMTEST_BROWSER}) does not support JSPI. Only chromium-based browsers ({CHROMIUM_BASED_BROWSERS}) support JSPI today.')
5029-
if is_jspi(args) and self.is_wasm64():
5030-
self.skipTest('_emval_await fails')
50315029

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

test/test_other.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3258,6 +3258,7 @@ def test_embind_asyncify(self):
32583258
'': [['-sDYNAMIC_EXECUTION=1']],
32593259
'no_dynamic': [['-sDYNAMIC_EXECUTION=0']],
32603260
'dyncall': [['-sALLOW_MEMORY_GROWTH', '-sMAXIMUM_MEMORY=4GB']],
3261+
'wasm64': (['-sMEMORY64'],),
32613262
})
32623263
@requires_jspi
32633264
def test_embind_jspi(self, args):
@@ -3496,6 +3497,24 @@ def test_jspi_async_function(self):
34963497
'-Wno-experimental',
34973498
'--post-js=post.js'])
34983499

3500+
@requires_jspi
3501+
def test_jspi_bad_library_function(self):
3502+
create_file('lib.js', r'''
3503+
addToLibrary({
3504+
foo__async: true,
3505+
foo: function(f) {},
3506+
});
3507+
''')
3508+
create_file('main.c', r'''
3509+
#include <emscripten.h>
3510+
extern void foo();
3511+
EMSCRIPTEN_KEEPALIVE void test() {
3512+
foo();
3513+
}
3514+
''')
3515+
err = self.expect_fail([EMCC, 'main.c', '-o', 'out.js', '-sJSPI', '--js-library=lib.js', '-Wno-experimental',])
3516+
self.assertContained('error: foo is marked with the __async decorator but is not an async JS function.', err)
3517+
34993518
@requires_dev_dependency('typescript')
35003519
@parameterized({
35013520
'commonjs': [['-sMODULARIZE'], ['--module', 'commonjs', '--moduleResolution', 'node']],

0 commit comments

Comments
 (0)