Skip to content

Commit e6bc1f1

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 c718e68 commit e6bc1f1

File tree

10 files changed

+54
-22
lines changed

10 files changed

+54
-22
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,
@@ -332,7 +333,7 @@ return ${makeReturn64(await_ + body)};
332333
return `\
333334
${async_}function(${args}) {
334335
${argConversions}
335-
var ret = (() => { ${body} })();
336+
var ret = (${async_}() => { ${body} })();
336337
return ${makeReturn64(await_ + 'ret')};
337338
}`;
338339
}
@@ -535,8 +536,8 @@ function(${args}) {
535536
deps.push('setTempRet0');
536537
}
537538

538-
const isAsyncFunction = LibraryManager.library[symbol + '__async'];
539-
if (ASYNCIFY && isAsyncFunction) {
539+
const hasAsyncDecorator = LibraryManager.library[symbol + '__async'];
540+
if (ASYNCIFY && hasAsyncDecorator) {
540541
asyncFuncs.push(symbol);
541542
}
542543

@@ -671,6 +672,10 @@ function(${args}) {
671672
}
672673

673674
if (isFunction) {
675+
if (ASYNCIFY == 2 && hasAsyncDecorator && !isAsyncFunction(snippet)) {
676+
error(`'${symbol}' is marked with the __async decorator but is not an async JS function.`);
677+
}
678+
674679
snippet = processLibraryFunction(snippet, symbol, mangled, deps, isStub);
675680
addImplicitDeps(snippet, deps);
676681
if (CHECK_DEPS && !isUserSymbol) {
@@ -765,7 +770,7 @@ function(${args}) {
765770
}
766771
contentText += `\n${mangled}.sig = '${sig}';`;
767772
}
768-
if (ASYNCIFY && isAsyncFunction) {
773+
if (ASYNCIFY && hasAsyncDecorator) {
769774
assert(isFunction);
770775
contentText += `\n${mangled}.isAsync = true;`;
771776
}

src/lib/libasync.js

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

483483
emscripten_sleep__deps: ['$safeSetTimeout'],
484484
emscripten_sleep__async: true,
485-
emscripten_sleep: (ms) => Asyncify.handleSleep((wakeUp) => safeSetTimeout(wakeUp, ms)),
485+
emscripten_sleep: async (ms) => Asyncify.handleSleep((wakeUp) => safeSetTimeout(wakeUp, ms)),
486486

487487
emscripten_wget_data__deps: ['$asyncLoad', 'malloc'],
488488
emscripten_wget_data__async: true,
489-
emscripten_wget_data: (url, pbuffer, pnum, perror) => Asyncify.handleAsync(async () => {
489+
emscripten_wget_data: async (url, pbuffer, pnum, perror) => Asyncify.handleAsync(async () => {
490490
/* no need for run dependency, this is async but will not do any prepare etc. step */
491491
try {
492492
const byteArray = await asyncLoad(UTF8ToString(url));
@@ -503,7 +503,7 @@ addToLibrary({
503503

504504
emscripten_scan_registers__deps: ['$safeSetTimeout'],
505505
emscripten_scan_registers__async: true,
506-
emscripten_scan_registers: (func) => {
506+
emscripten_scan_registers: async (func) => {
507507
return Asyncify.handleSleep((wakeUp) => {
508508
// We must first unwind, so things are spilled to the stack. Then while
509509
// we are pausing we do the actual scan. After that we can resume. Note
@@ -591,7 +591,7 @@ addToLibrary({
591591

592592
emscripten_fiber_swap__deps: ["$Asyncify", "$Fibers", '$stackSave'],
593593
emscripten_fiber_swap__async: true,
594-
emscripten_fiber_swap: (oldFiber, newFiber) => {
594+
emscripten_fiber_swap: async (oldFiber, newFiber) => {
595595
if (ABORT) return;
596596
#if ASYNCIFY_DEBUG
597597
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
@@ -2617,7 +2617,7 @@ function wrapSyscallFunction(x, library, isWasi) {
26172617
post = handler + post;
26182618

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

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

src/lib/libemval.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -401,12 +401,19 @@ ${functionBody}
401401
#if ASYNCIFY
402402
_emval_await__deps: ['$Emval', '$Asyncify'],
403403
_emval_await__async: true,
404+
#if ASYNCIFY == 1
404405
_emval_await: (promise) => {
405406
return Asyncify.handleAsync(async () => {
406407
var value = await Emval.toValue(promise);
407408
return Emval.toHandle(value);
408409
});
409410
},
411+
#else
412+
_emval_await: async (promise) => {
413+
var value = await Emval.toValue(promise);
414+
return Emval.toHandle(value);
415+
},
416+
#endif
410417
#endif
411418

412419
_emval_iter_begin__deps: ['$Emval'],

src/lib/libidbstore.js

Lines changed: 5 additions & 5 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') }}};

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/libwasi.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -541,7 +541,7 @@ var WasiLibrary = {
541541
},
542542

543543
fd_sync__async: true,
544-
fd_sync: (fd) => {
544+
fd_sync: {{{ asyncIf(ASYNCIFY) }}} (fd) => {
545545
#if SYSCALLS_REQUIRE_FILESYSTEM
546546
var stream = SYSCALLS.getStreamFromFD(fd);
547547
var rtn = stream.stream_ops?.fsync?.(stream);

test/codesize/test_codesize_hello_dylink_all.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
2-
"a.out.js": 244848,
2+
"a.out.js": 244853,
33
"a.out.nodebug.wasm": 574227,
4-
"total": 819075,
4+
"total": 819080,
55
"sent": [
66
"IMG_Init",
77
"IMG_Load",

test/test_jslib.py

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
from subprocess import PIPE
88

99
from common import RunnerCore, create_file, read_file, test_file
10-
from decorators import also_with_wasm64, also_without_bigint, parameterized
10+
from decorators import also_with_wasm64, also_without_bigint, parameterized, requires_jspi
1111

1212
from tools.shared import EMCC
1313
from tools.utils import delete_file
@@ -719,3 +719,22 @@ def test_jslib_version_check(self):
719719
#endif
720720
''')
721721
self.assert_fail([EMCC, '--js-library=libfoo.js'], 'error: libfoo.js:3: #error "library does not support emscripten > 3.0.0"')
722+
723+
@requires_jspi
724+
def test_jslib_jspi_missing_async(self):
725+
create_file('lib.js', r'''
726+
addToLibrary({
727+
foo__async: true,
728+
foo: function(f) {},
729+
});
730+
''')
731+
create_file('main.c', r'''
732+
#include <emscripten.h>
733+
extern void foo();
734+
EMSCRIPTEN_KEEPALIVE void test() {
735+
foo();
736+
}
737+
''')
738+
err = self.expect_fail([EMCC, 'main.c', '-o', 'out.js', '-sJSPI', '--js-library=lib.js', '-Wno-experimental'])
739+
self.assertContained('error: \'foo\' is marked with the __async decorator but is not an async JS function.', err)
740+

test/test_other.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3292,10 +3292,11 @@ def test_embind_asyncify(self):
32923292
''')
32933293
self.do_runf('main.cpp', 'done', cflags=['-lembind', '-sASYNCIFY', '--post-js', 'post.js'])
32943294

3295+
@also_with_wasm64
32953296
@parameterized({
3296-
'': [['-sDYNAMIC_EXECUTION=1']],
3297-
'no_dynamic': [['-sDYNAMIC_EXECUTION=0']],
3298-
'dyncall': [['-sALLOW_MEMORY_GROWTH', '-sMAXIMUM_MEMORY=4GB']],
3297+
'': (['-sDYNAMIC_EXECUTION=1'],),
3298+
'no_dynamic': (['-sDYNAMIC_EXECUTION=0'],),
3299+
'dyncall': (['-sALLOW_MEMORY_GROWTH', '-sMAXIMUM_MEMORY=4GB'],),
32993300
})
33003301
@requires_jspi
33013302
def test_embind_jspi(self, args):

0 commit comments

Comments
 (0)