Skip to content

Commit 4d22ffe

Browse files
authored
[jspi] Switch to new JSPI API. (#21815)
Differences with the new API: - The JS engine now takes care of the suspender, so we don't need to modify the wasm file with binaryen. - Imports and exports and are now marked as async with WebAssembly.Suspending and WebAssembly.promising wrappers.
1 parent 228af1a commit 4d22ffe

File tree

8 files changed

+32
-91
lines changed

8 files changed

+32
-91
lines changed

ChangeLog.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,9 @@ See docs/process.md for more on how version tagging works.
2020

2121
3.1.61 (in development)
2222
-----------------------
23+
- The JSPI feature now uses the updated browser API for JSPI (available in
24+
Chrome v126+). To support older versions of Chrome use Emscripten version
25+
3.1.60 or earlier.
2326

2427
3.1.60 - 05/20/24
2528
-----------------

src/closure-externs/closure-externs.js

Lines changed: 11 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,16 @@ WebAssembly.Memory.prototype.buffer;
112112
* @type {number}
113113
*/
114114
WebAssembly.Table.prototype.length;
115+
/**
116+
* @param {!Function} func
117+
* @returns {Function}
118+
*/
119+
WebAssembly.promising = function(func) {};
120+
/**
121+
* @constructor
122+
* @param {!Function} func
123+
*/
124+
WebAssembly.Suspending = function(func) {};
115125

116126
/**
117127
* @record
@@ -125,26 +135,13 @@ FunctionType.prototype.parameters;
125135
* @type {Array<string>}
126136
*/
127137
FunctionType.prototype.results;
128-
/**
129-
* @record
130-
*/
131-
function FunctionUsage() {}
132-
/**
133-
* @type {string|undefined}
134-
*/
135-
FunctionUsage.prototype.promising;
136-
/**
137-
* @type {string|undefined}
138-
*/
139-
FunctionUsage.prototype.suspending;
140138

141139
/**
142140
* @constructor
143141
* @param {!FunctionType} type
144142
* @param {!Function} func
145-
* @param {FunctionUsage=} usage
146143
*/
147-
WebAssembly.Function = function(type, func, usage) {};
144+
WebAssembly.Function = function(type, func) {};
148145
/**
149146
* @param {Function} func
150147
* @return {FunctionType}

src/embind/embind.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -798,6 +798,12 @@ var LibraryEmbind = {
798798
assert(!isAsync, 'Async bindings are only supported with JSPI.');
799799
#endif
800800

801+
#if ASYNCIFY == 2
802+
if (isAsync) {
803+
cppInvokerFunc = Asyncify.makeAsyncFunction(cppInvokerFunc);
804+
}
805+
#endif
806+
801807
var isClassMethodFunc = (argTypes[1] !== null && classType !== null);
802808

803809
// Free functions with signature "void function()" do not need an invoker that marshalls between wire types.

src/jsifier.mjs

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -629,12 +629,8 @@ function(${args}) {
629629
if ((EXPORT_ALL || EXPORTED_FUNCTIONS.has(mangled)) && !isStub) {
630630
contentText += `\nModule['${mangled}'] = ${mangled};`;
631631
}
632-
// Relocatable code needs signatures to create proper wrappers. Stack
633-
// switching needs signatures so we can create a proper
634-
// WebAssembly.Function with the signature for the Promise API.
635-
// TODO: For asyncify we could only add the signatures we actually need,
636-
// of async imports/exports.
637-
if (sig && (RELOCATABLE || ASYNCIFY == 2)) {
632+
// Relocatable code needs signatures to create proper wrappers.
633+
if (sig && RELOCATABLE) {
638634
if (!WASM_BIGINT) {
639635
sig = sig[0].replace('j', 'i') + sig.slice(1).replace(/j/g, 'ii');
640636
}

src/library_async.js

Lines changed: 3 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ addToLibrary({
4242
dbg('asyncify instrumenting imports');
4343
#endif
4444
#if ASSERTIONS && ASYNCIFY == 2
45-
assert('Suspender' in WebAssembly, 'JSPI not supported by current environment. Perhaps it needs to be enabled via flags?');
45+
assert('Suspending' in WebAssembly, 'JSPI not supported by current environment. Perhaps it needs to be enabled via flags?');
4646
#endif
4747
var importPattern = {{{ new RegExp(`^(${ASYNCIFY_IMPORTS_EXCEPT_JS_LIBS.map(x => x.split('.')[1]).join('|').replace(/\*/g, '.*')})$`) }}};
4848

@@ -52,21 +52,10 @@ addToLibrary({
5252
#if ASYNCIFY == 2
5353
// Wrap async imports with a suspending WebAssembly function.
5454
if (isAsyncifyImport) {
55-
#if ASSERTIONS
56-
assert(original.sig, `Missing __sig for ${x}`);
57-
#endif
58-
let type = sigToWasmTypes(original.sig);
5955
#if ASYNCIFY_DEBUG
6056
dbg('asyncify: suspendOnReturnedPromise for', x, original);
6157
#endif
62-
// Add space for the suspender promise that will be used in the
63-
// Wasm wrapper function.
64-
type.parameters.unshift('externref');
65-
imports[x] = original = new WebAssembly.Function(
66-
type,
67-
original,
68-
{ suspending: 'first' }
69-
);
58+
imports[x] = original = new WebAssembly.Suspending(original);
7059
}
7160
#endif
7261
#if ASSERTIONS && ASYNCIFY != 2 // We cannot apply assertions with stack switching, as the imports must not be modified from suspender.suspendOnReturnedPromise TODO find a way
@@ -454,20 +443,7 @@ addToLibrary({
454443
#if ASYNCIFY_DEBUG
455444
dbg('asyncify: returnPromiseOnSuspend for', original);
456445
#endif
457-
// TODO: remove `WebAssembly.Function.type` call when the new API is ready on all the testers.
458-
var type = original.type ? original.type() : WebAssembly.Function.type(original);
459-
var parameters = type.parameters;
460-
var results = type.results;
461-
#if ASSERTIONS
462-
assert(results.length !== 0, 'There must be a return result')
463-
assert(parameters[0] === 'externref', 'First param must be externref.');
464-
#endif
465-
// Remove the extern ref.
466-
parameters.shift();
467-
return new WebAssembly.Function(
468-
{ parameters , results: ['externref'] },
469-
original,
470-
{ promising : 'first' });
446+
return WebAssembly.promising(original);
471447
},
472448
#endif
473449
},

system/include/emscripten/bind.h

Lines changed: 7 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -454,25 +454,6 @@ struct Invoker<ReturnPolicy, void, Args...> {
454454
}
455455
};
456456

457-
namespace async {
458-
459-
template<typename F, F f> struct Wrapper;
460-
template<typename ReturnType, typename... Args, ReturnType(*f)(Args...)>
461-
struct Wrapper<ReturnType(*)(Args...), f> {
462-
EMSCRIPTEN_KEEPALIVE static ReturnType invoke(Args... args) {
463-
return f(args...);
464-
}
465-
};
466-
467-
} // end namespace async
468-
469-
template<typename T, typename... Policies>
470-
using maybe_wrap_async = typename std::conditional<
471-
isAsync<Policies...>::value,
472-
async::Wrapper<decltype(&T::invoke), &T::invoke>,
473-
T
474-
>::type;
475-
476457
template<typename ReturnPolicy, typename FunctorType, typename ReturnType, typename... Args>
477458
struct FunctorInvoker {
478459
static typename internal::BindingType<ReturnType>::WireType invoke(
@@ -601,8 +582,7 @@ void function(const char* name, ReturnType (*fn)(Args...), Policies...) {
601582
using namespace internal;
602583
typename WithPolicies<Policies...>::template ArgTypeList<ReturnType, Args...> args;
603584
using ReturnPolicy = GetReturnValuePolicy<ReturnType, Policies...>::tag;
604-
using OriginalInvoker = Invoker<ReturnPolicy, ReturnType, Args...>;
605-
auto invoke = &maybe_wrap_async<OriginalInvoker, Policies...>::invoke;
585+
auto invoke = Invoker<ReturnPolicy, ReturnType, Args...>::invoke;
606586
_embind_register_function(
607587
name,
608588
args.getCount(),
@@ -1469,8 +1449,7 @@ struct RegisterClassMethod<ReturnType (ClassType::*)(Args...)> {
14691449
static void invoke(const char* methodName,
14701450
ReturnType (ClassType::*memberFunction)(Args...)) {
14711451
using ReturnPolicy = GetReturnValuePolicy<ReturnType, Policies...>::tag;
1472-
using OriginalInvoker = MethodInvoker<ReturnPolicy, decltype(memberFunction), ReturnType, ClassType*, Args...>;
1473-
auto invoke = &maybe_wrap_async<OriginalInvoker, Policies...>::invoke;
1452+
auto invoke = MethodInvoker<ReturnPolicy, decltype(memberFunction), ReturnType, ClassType*, Args...>::invoke;
14741453

14751454
typename WithPolicies<Policies...>::template ArgTypeList<ReturnType, AllowedRawPointer<ClassType>, Args...> args;
14761455
_embind_register_class_function(
@@ -1499,8 +1478,7 @@ struct RegisterClassMethod<ReturnType (ClassType::*)(Args...) const> {
14991478
static void invoke(const char* methodName,
15001479
ReturnType (ClassType::*memberFunction)(Args...) const) {
15011480
using ReturnPolicy = GetReturnValuePolicy<ReturnType, Policies...>::tag;
1502-
using OriginalInvoker = MethodInvoker<ReturnPolicy, decltype(memberFunction), ReturnType, const ClassType*, Args...>;
1503-
auto invoke = &maybe_wrap_async<OriginalInvoker, Policies...>::invoke;
1481+
auto invoke = MethodInvoker<ReturnPolicy, decltype(memberFunction), ReturnType, const ClassType*, Args...>::invoke;
15041482

15051483
typename WithPolicies<Policies...>::template ArgTypeList<ReturnType, AllowedRawPointer<const ClassType>, Args...> args;
15061484
_embind_register_class_function(
@@ -1530,8 +1508,7 @@ struct RegisterClassMethod<ReturnType (*)(ThisType, Args...)> {
15301508
ReturnType (*function)(ThisType, Args...)) {
15311509
typename WithPolicies<Policies...>::template ArgTypeList<ReturnType, ThisType, Args...> args;
15321510
using ReturnPolicy = GetReturnValuePolicy<ReturnType, Policies...>::tag;
1533-
using OriginalInvoker = FunctionInvoker<ReturnPolicy, decltype(function), ReturnType, ThisType, Args...>;
1534-
auto invoke = &maybe_wrap_async<OriginalInvoker, Policies...>::invoke;
1511+
auto invoke = FunctionInvoker<ReturnPolicy, decltype(function), ReturnType, ThisType, Args...>::invoke;
15351512
_embind_register_class_function(
15361513
TypeID<ClassType>::get(),
15371514
methodName,
@@ -1559,8 +1536,7 @@ struct RegisterClassMethod<std::function<ReturnType (ThisType, Args...)>> {
15591536
std::function<ReturnType (ThisType, Args...)> function) {
15601537
typename WithPolicies<Policies...>::template ArgTypeList<ReturnType, ThisType, Args...> args;
15611538
using ReturnPolicy = GetReturnValuePolicy<ReturnType, Policies...>::tag;
1562-
using OriginalInvoker = FunctorInvoker<ReturnPolicy, decltype(function), ReturnType, ThisType, Args...>;
1563-
auto invoke = &maybe_wrap_async<OriginalInvoker, Policies...>::invoke;
1539+
auto invoke = FunctorInvoker<ReturnPolicy, decltype(function), ReturnType, ThisType, Args...>::invoke;
15641540
_embind_register_class_function(
15651541
TypeID<ClassType>::get(),
15661542
methodName,
@@ -1582,8 +1558,7 @@ struct RegisterClassMethod<ReturnType (ThisType, Args...)> {
15821558
Callable& callable) {
15831559
typename WithPolicies<Policies...>::template ArgTypeList<ReturnType, ThisType, Args...> args;
15841560
using ReturnPolicy = GetReturnValuePolicy<ReturnType, Policies...>::tag;
1585-
using OriginalInvoker = FunctorInvoker<ReturnPolicy, decltype(callable), ReturnType, ThisType, Args...>;
1586-
auto invoke = &maybe_wrap_async<OriginalInvoker, Policies...>::invoke;
1561+
auto invoke = FunctorInvoker<ReturnPolicy, decltype(callable), ReturnType, ThisType, Args...>::invoke;
15871562
_embind_register_class_function(
15881563
TypeID<ClassType>::get(),
15891564
methodName,
@@ -1866,8 +1841,7 @@ class class_ {
18661841

18671842
typename WithPolicies<Policies...>::template ArgTypeList<ReturnType, Args...> args;
18681843
using ReturnPolicy = GetReturnValuePolicy<ReturnType, Policies...>::tag;
1869-
using OriginalInvoker = internal::Invoker<ReturnPolicy, ReturnType, Args...>;
1870-
auto invoke = &maybe_wrap_async<OriginalInvoker, Policies...>::invoke;
1844+
auto invoke = internal::Invoker<ReturnPolicy, ReturnType, Args...>::invoke;
18711845
_embind_register_class_class_function(
18721846
TypeID<ClassType>::get(),
18731847
methodName,

test/test_other.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4942,7 +4942,6 @@ def test_valid_abspath_2(self):
49424942
else:
49434943
abs_include_path = '/nowhere/at/all'
49444944
cmd = [EMCC, test_file('hello_world.c'), '--valid-abspath', abs_include_path, '-I%s' % abs_include_path]
4945-
print(' '.join(cmd))
49464945
self.run_process(cmd)
49474946
self.assertContained('hello, world!', self.run_js('a.out.js'))
49484947

tools/link.py

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -52,10 +52,6 @@
5252
DEFAULT_ASYNCIFY_EXPORTS = [
5353
'main',
5454
'__main_argc_argv',
55-
# Embind's async template wrapper functions. These functions are usually in
56-
# the function pointer table and not called from exports, but we need to name
57-
# them so the JSPI pass can find and convert them.
58-
'_ZN10emscripten8internal5async*'
5955
]
6056

6157
VALID_ENVIRONMENTS = ('web', 'webview', 'worker', 'node', 'shell')
@@ -395,12 +391,6 @@ def check_human_readable_list(items):
395391
if settings.ASYNCIFY_ONLY:
396392
check_human_readable_list(settings.ASYNCIFY_ONLY)
397393
passes += ['--pass-arg=asyncify-onlylist@%s' % ','.join(settings.ASYNCIFY_ONLY)]
398-
elif settings.ASYNCIFY == 2:
399-
passes += ['--jspi']
400-
passes += ['--pass-arg=jspi-imports@%s' % ','.join(settings.ASYNCIFY_IMPORTS)]
401-
passes += ['--pass-arg=jspi-exports@%s' % ','.join(settings.ASYNCIFY_EXPORTS)]
402-
if settings.SPLIT_MODULE:
403-
passes += ['--pass-arg=jspi-split-module']
404394

405395
if settings.MEMORY64 == 2:
406396
passes += ['--memory64-lowering']

0 commit comments

Comments
 (0)