Skip to content

Fix late-binding symbols with JSPI (implementation 2) #24161

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: main
Choose a base branch
from

Conversation

hoodmane
Copy link
Collaborator

Late-binding symbols get a JS stub import that resolves the symbol and then makes an onward call. This breaks JSPI.

This is a second approach to solving the problem by using WebAssembly.promising and WebAssembly.Suspending with a JS trampoline. Unfortunately, as far as I can tell there is no way to make it work for both a promising entrypoint and a non-promising entrypoint. This is due to a change in the JSPI spec: early versions said that if a suspending import does not return a promise, the suspender was allowed to be null. However, the new version of JSPI eagerly traps if the suspender is null, even if the function does not return a promise.

Late-binding symbols get a JS stub import that resolves the symbol
and then makes an onward call. This breaks JSPI.

This is a second approach to solving the problem by using WebAssembly.promising
and WebAssembly.Suspending with a JS trampoline. Unfortunately, as far as I can
tell there is no way to make it work for both a promising entrypoint and a
non-promising entrypoint. This is due to a change in the JSPI spec: early versions
said that if a suspending import does not return a promise, the suspender was allowed
to be null. However, the new version of JSPI eagerly traps if the suspender is null,
even if the function does not return a promise.
}

EM_JS(void, js_trampoline, (), {
_not_promising();
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@kripken this part of the test would work in #23619 but doesn't work here. In particular, if we call into C from JS via an export that isn't in JSPI_EXPORTS then we fail when calling into the stub trampoline. In my "make the trampoline WebAssembly" version, it works fine.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Interesting, thanks... sounds like the JSPI spec changed and I haven't followed that closely.

@brendandahl is there some workaround for this issue?

If not, it sounds like this approach would need to add all late-binding symbols to JSPI_EXPORTS..?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think what would be needed would be to add all C functions that might call late-binding symbols to JSPI_EXPORTS? In this case the problem is fixed by adding -sJSPI_EXPORTS=not_promising.

If we wanted to go this way, we should probably make all exports JSPI_EXPORTS.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One thing we could do is make a selector function with body like:

try (result restype)
   local.get 0
   local.get 1
   call suspendingTrampoline
catch $SuspendError
   local.get 0
   local.get 1
   call nonsuspendingTrampoline
end

and then make both a suspending and nonsuspending version of the stub. But this still forces us to do most of the painful stuff in #23619 where we calculate the type and generate a dynamic wasm module.

hoodmane added a commit to hoodmane/emscripten that referenced this pull request Apr 21, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants