Skip to content

Commit c1d3f41

Browse files
clementperonclaude
andcommitted
gh-156780: Emscripten: move promising-main setup out of libpython
The FS.createAsyncInputDevice() definition and the resolveGlobalSymbol hook that wraps main() in WebAssembly.promising() only make sense when Python is the program, but they lived in libpython, so every embedder got them. PLATFORM_OBJS goes into LIBRARY_OBJS and so into libpython. Add PLATFORM_MAIN_OBJS for platform objects that belong to the interpreter alone, and move this setup to Programs/emscripten_beforemain.c. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
1 parent 465b3b8 commit c1d3f41

5 files changed

Lines changed: 108 additions & 88 deletions

File tree

Makefile.pre.in

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -970,9 +970,12 @@ clinic: check-clean-src
970970
clinic-tests: check-clean-src $(srcdir)/Lib/test/clinic.test.c
971971
$(PYTHON_FOR_REGEN) $(srcdir)/Tools/clinic/clinic.py -f $(srcdir)/Lib/test/clinic.test.c
972972

973+
# Objects linked into the interpreter only, not into libpython.
974+
PLATFORM_MAIN_OBJS= @PLATFORM_MAIN_OBJS@
975+
973976
# Build the interpreter
974-
$(BUILDPYTHON): Programs/python.o $(LINK_PYTHON_DEPS)
975-
$(LINKCC) $(PY_CORE_EXE_LDFLAGS) $(LINKFORSHARED) -o $@ Programs/python.o $(LINK_PYTHON_OBJS) $(LIBS) $(MODLIBS) $(SYSLIBS)
977+
$(BUILDPYTHON): Programs/python.o $(PLATFORM_MAIN_OBJS) $(LINK_PYTHON_DEPS)
978+
$(LINKCC) $(PY_CORE_EXE_LDFLAGS) $(LINKFORSHARED) -o $@ Programs/python.o $(PLATFORM_MAIN_OBJS) $(LINK_PYTHON_OBJS) $(LIBS) $(MODLIBS) $(SYSLIBS)
976979

977980
platform: $(PYTHON_FOR_BUILD_DEPS) pybuilddir.txt
978981
$(RUNSHARED) $(PYTHON_FOR_BUILD) -c 'import sys ; from sysconfig import get_platform ; print("%s-%d.%d" % (get_platform(), *sys.version_info[:2]))' >platform

Programs/emscripten_beforemain.c

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
/* Emscripten setup for when Python is the program. Linked into the
2+
* interpreter only, never into libpython.
3+
*/
4+
5+
#include <emscripten.h>
6+
#include <errno.h>
7+
8+
// Variant of EM_JS that does C preprocessor substitution on the body
9+
#define EM_JS_MACROS(ret, func_name, args, body...) \
10+
EM_JS(ret, func_name, args, body)
11+
EM_JS_MACROS(void, _PyEmscripten_BeforeMain_js, (void), {
12+
// Define FS.createAsyncInputDevice(), This is quite similar to
13+
// FS.createDevice() defined here:
14+
// https://github.com/emscripten-core/emscripten/blob/4.0.11/src/lib/libfs.js?plain=1#L1642
15+
// but instead of returning one byte at a time, the input() function should
16+
// return a Uint8Array. This makes the handler code simpler, the
17+
// `createAsyncInputDevice` simpler, and everything faster.
18+
FS.createAsyncInputDevice = function(parent, name, input) {
19+
parent = typeof parent == 'string' ? parent : FS.getPath(parent);
20+
var path = PATH.join2(parent, name);
21+
var mode = FS_getMode(true, false);
22+
FS.createDevice.major ||= 64;
23+
var dev = FS.makedev(FS.createDevice.major++, 0);
24+
async function getDataBuf() {
25+
var buf;
26+
try {
27+
buf = await input();
28+
} catch (e) {
29+
throw new FS.ErrnoError(EIO);
30+
}
31+
if (!buf?.byteLength) {
32+
throw new FS.ErrnoError(EAGAIN);
33+
}
34+
ops._dataBuf = buf;
35+
}
36+
37+
var ops = {
38+
_dataBuf: new Uint8Array(0),
39+
open(stream) {
40+
stream.seekable = false;
41+
},
42+
async readAsync(stream, buffer, offset, length, pos /* ignored */) {
43+
buffer = buffer.subarray(offset, offset + length);
44+
if (!ops._dataBuf.byteLength) {
45+
await getDataBuf();
46+
}
47+
var toRead = Math.min(ops._dataBuf.byteLength, buffer.byteLength);
48+
buffer.subarray(0, toRead).set(ops._dataBuf);
49+
buffer = buffer.subarray(toRead);
50+
ops._dataBuf = ops._dataBuf.subarray(toRead);
51+
if (toRead) {
52+
stream.node.atime = Date.now();
53+
}
54+
return toRead;
55+
},
56+
};
57+
FS.registerDevice(dev, ops);
58+
return FS.mkdev(path, mode, dev);
59+
};
60+
if (!WebAssembly.promising) {
61+
// No stack switching support =(
62+
return;
63+
}
64+
const origResolveGlobalSymbol = resolveGlobalSymbol;
65+
if (ENVIRONMENT_IS_NODE && !Module.onExit) {
66+
Module.onExit = (code) => process.exit(code);
67+
}
68+
// * wrap the main symbol with WebAssembly.promising,
69+
// * call exit_with_live_runtime() to prevent emscripten from shutting down
70+
// the runtime before the promise resolves,
71+
// * call onExit / process.exit ourselves, since exit_with_live_runtime()
72+
// prevented Emscripten from calling it normally.
73+
resolveGlobalSymbol = function (name, direct = false) {
74+
const orig = origResolveGlobalSymbol(name, direct);
75+
if (name === "main") {
76+
const main = WebAssembly.promising(orig.sym);
77+
orig.sym = (...args) => {
78+
(async () => {
79+
const ret = await main(...args);
80+
Module.onExit?.(ret);
81+
})();
82+
_emscripten_exit_with_live_runtime();
83+
};
84+
}
85+
return orig;
86+
};
87+
})
88+
89+
EM_JS_DEPS(_PyEmscripten_BeforeMain,
90+
"$FS,$PATH,$FS_getMode,$resolveGlobalSymbol,"
91+
"emscripten_exit_with_live_runtime");
92+
93+
__attribute__((constructor)) void _PyEmscripten_BeforeMain(void) {
94+
_PyEmscripten_BeforeMain_js();
95+
}

Python/emscripten_syscalls.c

Lines changed: 0 additions & 86 deletions
Original file line numberDiff line numberDiff line change
@@ -54,92 +54,6 @@ __attribute__((constructor)) void __syscall_init_umask(void) {
5454
#define EM_JS_MACROS(ret, func_name, args, body...) \
5555
EM_JS(ret, func_name, args, body)
5656

57-
EM_JS_MACROS(void, _emscripten_promising_main_js, (void), {
58-
// Define FS.createAsyncInputDevice(), This is quite similar to
59-
// FS.createDevice() defined here:
60-
// https://github.com/emscripten-core/emscripten/blob/4.0.11/src/lib/libfs.js?plain=1#L1642
61-
// but instead of returning one byte at a time, the input() function should
62-
// return a Uint8Array. This makes the handler code simpler, the
63-
// `createAsyncInputDevice` simpler, and everything faster.
64-
FS.createAsyncInputDevice = function(parent, name, input) {
65-
parent = typeof parent == 'string' ? parent : FS.getPath(parent);
66-
var path = PATH.join2(parent, name);
67-
var mode = FS_getMode(true, false);
68-
FS.createDevice.major ||= 64;
69-
var dev = FS.makedev(FS.createDevice.major++, 0);
70-
async function getDataBuf() {
71-
var buf;
72-
try {
73-
buf = await input();
74-
} catch (e) {
75-
throw new FS.ErrnoError(EIO);
76-
}
77-
if (!buf?.byteLength) {
78-
throw new FS.ErrnoError(EAGAIN);
79-
}
80-
ops._dataBuf = buf;
81-
}
82-
83-
var ops = {
84-
_dataBuf: new Uint8Array(0),
85-
open(stream) {
86-
stream.seekable = false;
87-
},
88-
async readAsync(stream, buffer, offset, length, pos /* ignored */) {
89-
buffer = buffer.subarray(offset, offset + length);
90-
if (!ops._dataBuf.byteLength) {
91-
await getDataBuf();
92-
}
93-
var toRead = Math.min(ops._dataBuf.byteLength, buffer.byteLength);
94-
buffer.subarray(0, toRead).set(ops._dataBuf);
95-
buffer = buffer.subarray(toRead);
96-
ops._dataBuf = ops._dataBuf.subarray(toRead);
97-
if (toRead) {
98-
stream.node.atime = Date.now();
99-
}
100-
return toRead;
101-
},
102-
};
103-
FS.registerDevice(dev, ops);
104-
return FS.mkdev(path, mode, dev);
105-
};
106-
if (!WebAssembly.promising) {
107-
// No stack switching support =(
108-
return;
109-
}
110-
const origResolveGlobalSymbol = resolveGlobalSymbol;
111-
if (ENVIRONMENT_IS_NODE && !Module.onExit) {
112-
Module.onExit = (code) => process.exit(code);
113-
}
114-
// * wrap the main symbol with WebAssembly.promising,
115-
// * call exit_with_live_runtime() to prevent emscripten from shutting down
116-
// the runtime before the promise resolves,
117-
// * call onExit / process.exit ourselves, since exit_with_live_runtime()
118-
// prevented Emscripten from calling it normally.
119-
resolveGlobalSymbol = function (name, direct = false) {
120-
const orig = origResolveGlobalSymbol(name, direct);
121-
if (name === "main") {
122-
const main = WebAssembly.promising(orig.sym);
123-
orig.sym = (...args) => {
124-
(async () => {
125-
const ret = await main(...args);
126-
Module.onExit?.(ret);
127-
})();
128-
_emscripten_exit_with_live_runtime();
129-
};
130-
}
131-
return orig;
132-
};
133-
})
134-
135-
EM_JS_DEPS(_emscripten_promising_main,
136-
"$FS,$PATH,$FS_getMode,$resolveGlobalSymbol,"
137-
"emscripten_exit_with_live_runtime");
138-
139-
__attribute__((constructor)) void _emscripten_promising_main(void) {
140-
_emscripten_promising_main_js();
141-
}
142-
14357

14458
#define IOVEC_T_BUF_OFFSET 0
14559
#define IOVEC_T_BUF_LEN_OFFSET 4

configure

Lines changed: 4 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

configure.ac

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5427,9 +5427,12 @@ fi
54275427
dnl Platform-specific C and header files.
54285428
PLATFORM_HEADERS=
54295429
PLATFORM_OBJS=
5430+
dnl Objects linked into the interpreter only, not into libpython.
5431+
PLATFORM_MAIN_OBJS=
54305432

54315433
AS_CASE([$ac_sys_system],
54325434
[Emscripten], [
5435+
AS_VAR_APPEND([PLATFORM_MAIN_OBJS], [' Programs/emscripten_beforemain.o'])
54335436
AS_VAR_APPEND([PLATFORM_OBJS], [' Python/emscripten_signal.o Python/emscripten_trampoline.o Python/emscripten_trampoline_wasm.o'])
54345437
AS_VAR_IF([enable_emscripten_syscalls], [yes], [
54355438
AS_VAR_APPEND([PLATFORM_OBJS], [' Python/emscripten_syscalls.o'])
@@ -5439,6 +5442,7 @@ AS_CASE([$ac_sys_system],
54395442
)
54405443
AC_SUBST([PLATFORM_HEADERS])
54415444
AC_SUBST([PLATFORM_OBJS])
5445+
AC_SUBST([PLATFORM_MAIN_OBJS])
54425446

54435447
# -I${DLINCLDIR} is added to the compile rule for importdl.o
54445448
AC_SUBST([DLINCLDIR])

0 commit comments

Comments
 (0)