Repository navigation
Conversation
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## master #19594 +/- ##
=======================================
Coverage 98.59% 98.59%
=======================================
Files 182 182
Lines 23316 23316
Branches 5 5
=======================================
Hits 22988 22988
Misses 327 327
Partials 1 1
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
Signed-off-by: Nicholas H.Tollervey <[email protected]>
Signed-off-by: Nicholas H.Tollervey <[email protected]>
Signed-off-by: Nicholas H.Tollervey <[email protected]>
f6de36b to
fba8faa
Compare
|
Code size report: |
Understatement of the year 😆 I think it's the right call. Careful management of suspended state is not a can of worms we want to open.
Is this true? My JSPI builds are about 700k vs my Asyncify builds at 1.7MB (though I'm bolting on a lot of stuff that might spiral Asyncify's instrumentation out of control). Probably wouldn't hurt to have code size reports for WASM?
Mine would be: 😆 |
|
@Gadgetoid - so I've just built three webassembly variants: JSPI, PyScript and standard (asyncify). Here's the sizes for all the assets ( No surprises that asyncify is much larger than the others. The difference between PyScript and JSPI (which is itself just the PyScript variant with the new JSPI code) is a couple of kilobytes more in JSPI's mjs file for the Emscripten JSPI "stuff". Interestingly, JSPI's wasm file is actually smaller than PyScript's because JSPI requires wasm native longjmp instead of the JavaScript trampoline mechanism (JSPI can only suspend a WASM only stack), which actually removes code. @dpgeorge are you happy with these numbers? |
Summary
Related to @Gadgetoid's work in #19427 which formed the basis of a technical investigation found in this gist.
This PR adds minimal JavaScript Promise Integration (JSPI) to MicroPython's webassembly port. This feature will only work with browsers that support JSPI or recent versions of Node (24+) that also support it.
JavaScript Promise Integration (JSPI) is a WebAssembly standard, now shipping in V8-based browsers and coming soon to Firefox and Safari. It lets a running WebAssembly computation suspend on a JavaScript Promise and resume when it resolves. Synchronous-looking Python appears to safely block in the asynchronous browser world (
result = run_sync(fetch(...))) without actually blocking the main thread or incurring Asyncify's historical penalty in binary size and speed. It is the standards-based mechanism that efficiently lets sync-shaped code interoperate cleanly with the promise-based web platform.The PR adds
jsffi.run_sync()andjsffi.can_run_sync()with Pyodide API parity, delivered as a newjspibuild variant.runPythonAsync()is the sole promising entry; one suspension in flight, enforced globally; both functions exist on every build with graceful degradation.Note: this proposal permits exactly one suspension in flight at a time - a deliberate, justified divergence from Pyodide (see the decision in section C2 of the linked gist) that trades an exotic capability for a drastically simpler / smaller implementation.
Testing
Testing is entirely contained within the
webassemblyport.Prerequisits
Emscripten:
Node (25 or later):
Build and test
Expect
emcc: warning: -sJSPI (ASYNCIFY=2) is still experimentalduring the build. This labels Emscripten's toolchain integration, not the JSPI standard itself (which is W3C Phase 4 and shipping in all three engine families).NB: If you're using Node24 you'll need to add
--experimental-wasm-jspito allnodecommands.Smoke tests
Start the REPL:
Then try this:
A picture is worth a thousand words:
jspi_repl.mp4
Note: CTRL-C and
exit()don't appear to work with the node based REPL.I've also attached
xterm-input-demo.zip. Copymicropython.mjsandmicropython.wasmfromports/webassembly/build-jspi/into theassetsdirectory inside the unzipped directory, then serve the project root over HTTP (e.g.python3 -m http.server) and openindex.htmlin a JSPI-capable browser via http://localhost:8000 (e.g. recent Chrome).📦 xterm-input-demo.zip
This demonstrates the use of JSPI in the browser to run blocking Python code on the browser's main thread without blocking the main thread. I replace the builtin
inputfunction with one that interacts with Xterm via a promise, resolved when the user's input is submitted.It's the canonical impossible thing - blocking
input()on the browser's main thread beloved by literally every introduction to Python - working in stock-shaped Python, with no worker, no Asyncify, no SharedArrayBuffer faffing about, or incomprehensible HTTP headers. Nice'n'simple. 🙂It's the two-line argument for the whole PR, and the seed of BFP's need to avoid all the web-worker complexity of PyScript via JSPI. 🎉 🤗 💪 🚀
Again, a picture is worth a thousand words:
jspi_browser.mp4
Trade-offs and Alternatives
The resulting assets are going to be marginally bigger, but within what I suspect are acceptable limits.
Generative AI
I used generative AI tools when creating this PR, but a human has checked the code and is responsible for the code and the description above.
Specifically: