Skip to content

Commit d6bb5ac

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 6dde64a commit d6bb5ac

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
}
@@ -681,6 +682,10 @@ function(${args}) {
681682
snippet = stringifyWithFunctions(snippet);
682683
addImplicitDeps(snippet, deps);
683684
} else if (isFunction) {
685+
if (ASYNCIFY == 2 && hasAsyncDecorator && !isAsyncFunction(snippet)) {
686+
error(`'${symbol}' is marked with the __async decorator but is not an async JS function.`);
687+
}
688+
684689
snippet = processLibraryFunction(snippet, symbol, mangled, deps, isStub);
685690
addImplicitDeps(snippet, deps);
686691
if (CHECK_DEPS && !isUserSymbol) {
@@ -775,7 +780,7 @@ function(${args}) {
775780
}
776781
contentText += `\n${mangled}.sig = '${sig}';`;
777782
}
778-
if (ASYNCIFY && isAsyncFunction) {
783+
if (ASYNCIFY && hasAsyncDecorator) {
779784
contentText += `\n${mangled}.isAsync = true;`;
780785
}
781786
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
@@ -2622,7 +2622,7 @@ function wrapSyscallFunction(x, library, isWasi) {
26222622
post = handler + post;
26232623

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

26282628
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
@@ -4982,8 +4982,6 @@ def test_embind_with_pthreads(self):
49824982
def test_embind(self, args):
49834983
if is_jspi(args) and not is_chrome():
49844984
self.skipTest(f'Current browser ({common.EMTEST_BROWSER}) does not support JSPI. Only chromium-based browsers ({CHROMIUM_BASED_BROWSERS}) support JSPI today.')
4985-
if is_jspi(args) and self.is_wasm64():
4986-
self.skipTest('_emval_await fails')
49874985

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

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)