Skip to content

Commit 846d815

Browse files
clementperonclaude
andcommitted
gh-156780: Emscripten: gate the suspending syscalls on the main() wrapper
Suspending in fd_read or poll needs a promising entry point, which libpython no longer sets up itself. Check Module.Py_EmscriptenStackSwitching, set by the interpreter's wrapper, instead of WebAssembly.promising alone. An embedder with its own promising entry point sets the flag. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
1 parent 76d45d9 commit 846d815

3 files changed

Lines changed: 11 additions & 3 deletions

File tree

Modules/_testinternalcapi.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2961,7 +2961,7 @@ EM_JS(int, emscripten_set_up_async_input_device_js, (void), {
29612961
await sleep(5);
29622962
return bufs[(idx ++) % 3];
29632963
});
2964-
return !!WebAssembly.promising;
2964+
return !!Module.Py_EmscriptenStackSwitching;
29652965
});
29662966

29672967
static PyObject *

Programs/emscripten_beforemain.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ EM_JS(void, _PyEmscripten_BeforeMain_js, (void), {
1818
// promising() needs the raw export; _main may be a JS wrapper around it.
1919
const main = WebAssembly.promising(wasmExports.__main_argc_argv);
2020
_main = (...args) => {
21+
Module.Py_EmscriptenStackSwitching = true;
2122
// Exit the way callMain() would have, once main() is actually done.
2223
main(...args).then((ret) => exitJS(ret, true)).catch(handleException);
2324
// Unwind to callMain() without letting it exit: main() is still

Python/emscripten_syscalls.c

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -124,13 +124,20 @@ _Static_assert(sizeof(__wasi_iovec_t) == IOVEC_T_SIZE,
124124
// If the stream has a readAsync handler, read to buffer defined in iovs, write
125125
// number of bytes read to *nread, and return a promise that resolves to the
126126
// errno. Otherwise, return null.
127+
//
128+
// Reading from an async input device and poll() suspend the wasm stack
129+
// instead of blocking when main() runs under WebAssembly.promising, which
130+
// Programs/emscripten_beforemain.c arranges for the interpreter. An embedder
131+
// running its own promising entry point opts in with
132+
// Module.Py_EmscriptenStackSwitching = true;
133+
// Otherwise these calls keep their synchronous behavior.
127134
EM_JS_MACROS(__externref_t, __maybe_fd_read_async, (
128135
__wasi_fd_t fd,
129136
const __wasi_iovec_t *iovs,
130137
size_t iovcnt,
131138
__wasi_size_t *nread
132139
), {
133-
if (!WebAssembly.promising) {
140+
if (!Module.Py_EmscriptenStackSwitching) {
134141
return null;
135142
}
136143
var stream;
@@ -214,7 +221,7 @@ _Static_assert(offsetof(struct pollfd, revents) == 6, "Unepxected pollfd struct
214221
_Static_assert(sizeof(struct pollfd) == 8, "Unepxected pollfd struct layout");
215222

216223
EM_JS_MACROS(__externref_t, __maybe_poll_async, (intptr_t fds, int nfds, int timeout), {
217-
if (!WebAssembly.promising) {
224+
if (!Module.Py_EmscriptenStackSwitching) {
218225
return null;
219226
}
220227
return (async function() {

0 commit comments

Comments
 (0)