Skip to content
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

Fix late-binding symbols with JSPI #23619

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

Conversation

hoodmane
Copy link
Collaborator

@hoodmane hoodmane commented Feb 7, 2025

Late-binding symbols get a JS stub import that resolves the symbol and then makes an onward call. This breaks JSPI. (It also canonicalizes NaNs by round tripping them through JS).

To fix this, we have to walk the import table and make wasm module stubs for each of the missing function-type symbols. These stubs will call a JS function to resolve the symbol but then insert it into a table and return control back to the wasm stub which has to make the onward call.

This currently relies on --experimental-wasm-type-reflection. To avoid that, we will need to parse the import table of the wasm binary manually. Also, without wasm-type-reflection, we have no way to handle the case where someone calls loadWebAssemblyModule() on a WebAssembly module instead of a Uint8Array.

This half way fixes #23395. I say half way because to be truly fixed the solution shouldn't rely on wasm type reflection. See also #23600.

@hoodmane hoodmane requested review from brendandahl and sbc100 and removed request for brendandahl February 7, 2025 12:13
@hoodmane hoodmane force-pushed the jspi-dylink-stubs branch 4 times, most recently from 55ff7e0 to 33707ea Compare February 7, 2025 12:16
Late-binding symbols get a JS stub import that resolves the symbol
and then makes an onward call. This breaks JSPI. (It also canonicalizes
NaNs by round tripping them through JS).

To fix this, we have to walk the import table and make wasm module stubs
for each of the missing function-type symbols. These stubs will call a JS
function to resolve the symbol but then insert it into a table and return
control back to the wasm stub which has to make the onward call.

This currently relies on --experimental-wasm-type-reflection. To avoid that,
we need to parse the import table of the wasm binary manually. Also, without
wasm-type-reflection, we have no way to handle the case where someone calls
`loadWebAssemblyModule()` on a WebAssembly module instead of a Uint8Array.
@sbc100
Copy link
Collaborator

sbc100 commented Feb 7, 2025

I say half way because to be truly fixed the solution shouldn't rely on wasm type reflection.

Why do you say this? Is that simply because wasm type reflection is not standardized yet? Presumably if it was standardized this would be good tool to solve this problem?

test/test_core.py Outdated Show resolved Hide resolved
return WebAssembly.instantiate(binary, info).then(
(result) => postInstantiation(result.module, result.instance)
);
return (async function() {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Hmm.. nice trick to use await inside a function that is not async..

Copy link
Collaborator

@sbc100 sbc100 left a comment

Choose a reason for hiding this comment

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

Oh wow, we really have to jump through a bunch of hoops to avoid JS thunks huh?

src/lib/libdylink.js Show resolved Hide resolved
src/lib/libdylink.js Show resolved Hide resolved
src/lib/libdylink.js Outdated Show resolved Hide resolved
sig += lookup[v];
}
return sig;
},
Copy link
Collaborator

Choose a reason for hiding this comment

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

Can any of this new code be shared with the existing code for binary module creation in in libraryadd_function.js?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Perhaps it would make sense to factor out the wasm prelude (magic + version) as an export from libaddfunction. Other than that, the only code that's really shared is generateFuncType which we already extracted as a separate function.

Copy link
Collaborator Author

@hoodmane hoodmane Feb 7, 2025

Choose a reason for hiding this comment

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

wasmSigToEmscripten is the inverse function of sigToWasmTypes in libaddfunction.js. In Pyodide I have a runtime_wasmfile for producing wasm trampolines like this:
https://github.com/pyodide/pyodide/blob/main/src/core/stack_switching/runtime_wasm.mjs

I could eventually upstream a bit more of this. It's not great for code size if we only have a few trampolines but it certainly leads to more readable code. Here's an example where I make invoke_sig functions with it:
https://github.com/pyodide/pyodide/blob/main/src/core/stack_switching/create_invokes.mjs

I think I might copy this code into libffi because I've been intending to make libffi compatible with stack switching.

@hoodmane
Copy link
Collaborator Author

hoodmane commented Feb 7, 2025

Yes in particular my concern is that I expect jspi to become available without a feature flag before wasm-type-reflection so if we depend on it there will be engines that otherwise would be able to use our jspi but can't because they haven't yet shipped type reflection.

Presumably if it was standardized this would be good tool to solve this problem?

Absolutely it's the right tool.

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.

JSPI and dynamic linking messed up by stub trampoline
2 participants