gh-124621: Emscripten: Add support for async input devices (GH-136822)

This is useful for implementing proper `input()`. It requires the
JavaScript engine to support the wasm JSPI spec which is now stage 4.
It is supported on Chrome since version 137 and on Firefox and node
behind a flag.

We override the `__wasi_fd_read()` syscall with our own variant that
checks for a readAsync operation. If it has it, we use our own async
variant of `fd_read()`, otherwise we use the original `fd_read()`.
We also add a variant of `FS.createDevice()` called
`FS.createAsyncInputDevice()`.

Finally, if JSPI is available, we wrap the `main()` symbol with
`WebAssembly.promising()` so that we can stack switch from `fd_read()`.
If JSPI is not available, attempting to read from an AsyncInputDevice
will raise an `OSError`.
This commit is contained in:
Hood Chatham 2025-07-19 17:14:29 +02:00 committed by GitHub
parent 1ba23244f3
commit 7ae4749d06
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 245 additions and 1 deletions

View file

@ -2345,6 +2345,37 @@ incref_decref_delayed(PyObject *self, PyObject *op)
Py_RETURN_NONE;
}
#ifdef __EMSCRIPTEN__
#include "emscripten.h"
EM_JS(int, emscripten_set_up_async_input_device_js, (void), {
let idx = 0;
const encoder = new TextEncoder();
const bufs = [
encoder.encode("ab\n"),
encoder.encode("fi\n"),
encoder.encode("xy\n"),
];
function sleep(t) {
return new Promise(res => setTimeout(res, t));
}
FS.createAsyncInputDevice("/dev", "blah", async () => {
await sleep(5);
return bufs[(idx ++) % 3];
});
return !!WebAssembly.promising;
});
static PyObject *
emscripten_set_up_async_input_device(PyObject *self, PyObject *Py_UNUSED(ignored)) {
if (emscripten_set_up_async_input_device_js()) {
Py_RETURN_TRUE;
} else {
Py_RETURN_FALSE;
}
}
#endif
static PyMethodDef module_functions[] = {
{"get_configs", get_configs, METH_NOARGS},
{"get_recursion_depth", get_recursion_depth, METH_NOARGS},
@ -2447,6 +2478,9 @@ static PyMethodDef module_functions[] = {
{"is_static_immortal", is_static_immortal, METH_O},
{"incref_decref_delayed", incref_decref_delayed, METH_O},
GET_NEXT_DICT_KEYS_VERSION_METHODDEF
#ifdef __EMSCRIPTEN__
{"emscripten_set_up_async_input_device", emscripten_set_up_async_input_device, METH_NOARGS},
#endif
{NULL, NULL} /* sentinel */
};