Skip to content

Commit 2c4b63e

Browse files
clementperonclaude
andcommitted
gh-156780: Emscripten: move main() wrapping out of libpython
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
1 parent 465b3b8 commit 2c4b63e

5 files changed

Lines changed: 68 additions & 45 deletions

File tree

Makefile.pre.in

Lines changed: 11 additions & 5 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
@@ -1614,8 +1617,8 @@ regen-re: $(BUILDPYTHON)
16141617
# using Tools/build/generate_re_casefix.py
16151618
$(RUNSHARED) ./$(BUILDPYTHON) $(srcdir)/Tools/build/generate_re_casefix.py $(srcdir)/Lib/re/_casefix.py
16161619

1617-
Programs/_testembed: Programs/_testembed.o $(LINK_PYTHON_DEPS)
1618-
$(LINKCC) $(PY_CORE_EXE_LDFLAGS) $(LINKFORSHARED) -o $@ Programs/_testembed.o $(LINK_PYTHON_OBJS) $(LIBS) $(MODLIBS) $(SYSLIBS)
1620+
Programs/_testembed: Programs/_testembed.o $(PLATFORM_MAIN_OBJS) $(LINK_PYTHON_DEPS)
1621+
$(LINKCC) $(PY_CORE_EXE_LDFLAGS) $(LINKFORSHARED) -o $@ Programs/_testembed.o $(PLATFORM_MAIN_OBJS) $(LINK_PYTHON_OBJS) $(LIBS) $(MODLIBS) $(SYSLIBS)
16191622

16201623
############################################################################
16211624
# "Bootstrap Python" used to run Programs/_freeze_module.py
@@ -2235,7 +2238,7 @@ regen-slots: Python/slots.toml
22352238
$(PYTHON_FOR_REGEN) $(srcdir)/Tools/build/generate_slots.py \
22362239
--generate-all
22372240

2238-
$(LIBRARY_OBJS) $(MODOBJS) Programs/python.o: $(PYTHON_HEADERS)
2241+
$(LIBRARY_OBJS) $(MODOBJS) Programs/python.o $(PLATFORM_MAIN_OBJS): $(PYTHON_HEADERS)
22392242

22402243

22412244
######################################################################
@@ -2961,6 +2964,9 @@ libainstall: all scripts
29612964
fi; \
29622965
fi; \
29632966
$(INSTALL_DATA) Programs/python.o $(DESTDIR)$(LIBPL)/python.o; \
2967+
for i in $(PLATFORM_MAIN_OBJS); do \
2968+
$(INSTALL_DATA) $$i $(DESTDIR)$(LIBPL); \
2969+
done; \
29642970
fi
29652971
$(INSTALL_DATA) Modules/config.c $(DESTDIR)$(LIBPL)/config.c
29662972
$(INSTALL_DATA) $(srcdir)/Modules/config.c.in $(DESTDIR)$(LIBPL)/config.c.in

Programs/emscripten_beforemain.c

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
/* Emscripten setup for when Python is the program. Linked into the
2+
* interpreter only, never into libpython.
3+
*
4+
* Runs main() under WebAssembly.promising so that libpython can suspend the
5+
* wasm stack in a syscall (see Python/emscripten_syscalls.c).
6+
*/
7+
8+
#include <emscripten.h>
9+
10+
EM_JS(void, _PyEmscripten_BeforeMain_js, (void), {
11+
if (!WebAssembly.promising) {
12+
// No stack switching support =(
13+
return;
14+
}
15+
if (ENVIRONMENT_IS_NODE && !Module.onExit) {
16+
Module.onExit = (code) => process.exit(code);
17+
}
18+
// promising() needs the raw export; _main may be a JS wrapper around it.
19+
const main = WebAssembly.promising(wasmExports.__main_argc_argv);
20+
_main = (...args) => {
21+
// Exit the way callMain() would have, once main() is actually done.
22+
main(...args).then((ret) => exitJS(ret, true)).catch(handleException);
23+
// Unwind to callMain() without letting it exit: main() is still
24+
// running on the promising stack.
25+
throw "unwind";
26+
};
27+
// callMain() takes the entry point from _main, or from wasmImports.main
28+
// when linked with -sMAIN_MODULE.
29+
if ("main" in wasmImports) {
30+
wasmImports.main = _main;
31+
}
32+
})
33+
34+
EM_JS_DEPS(_PyEmscripten_BeforeMain, "$exitJS,$handleException");
35+
36+
__attribute__((constructor)) void
37+
_PyEmscripten_BeforeMain(void)
38+
{
39+
_PyEmscripten_BeforeMain_js();
40+
}

Python/emscripten_syscalls.c

Lines changed: 10 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -54,13 +54,13 @@ __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.
57+
// Define FS.createAsyncInputDevice(), This is quite similar to
58+
// FS.createDevice() defined here:
59+
// https://github.com/emscripten-core/emscripten/blob/4.0.11/src/lib/libfs.js?plain=1#L1642
60+
// but instead of returning one byte at a time, the input() function should
61+
// return a Uint8Array. This makes the handler code simpler, the
62+
// `createAsyncInputDevice` simpler, and everything faster.
63+
EM_JS_MACROS(void, __fs_init_async_input_device_js, (void), {
6464
FS.createAsyncInputDevice = function(parent, name, input) {
6565
parent = typeof parent == 'string' ? parent : FS.getPath(parent);
6666
var path = PATH.join2(parent, name);
@@ -103,44 +103,14 @@ EM_JS_MACROS(void, _emscripten_promising_main_js, (void), {
103103
FS.registerDevice(dev, ops);
104104
return FS.mkdev(path, mode, dev);
105105
};
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-
};
133106
})
134107

135-
EM_JS_DEPS(_emscripten_promising_main,
136-
"$FS,$PATH,$FS_getMode,$resolveGlobalSymbol,"
137-
"emscripten_exit_with_live_runtime");
108+
EM_JS_DEPS(__fs_init_async_input_device, "$FS,$PATH,$FS_getMode");
138109

139-
__attribute__((constructor)) void _emscripten_promising_main(void) {
140-
_emscripten_promising_main_js();
110+
__attribute__((constructor)) void __fs_init_async_input_device(void) {
111+
__fs_init_async_input_device_js();
141112
}
142113

143-
144114
#define IOVEC_T_BUF_OFFSET 0
145115
#define IOVEC_T_BUF_LEN_OFFSET 4
146116
#define IOVEC_T_SIZE 8

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: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5427,10 +5427,12 @@ fi
54275427
dnl Platform-specific C and header files.
54285428
PLATFORM_HEADERS=
54295429
PLATFORM_OBJS=
5430+
PLATFORM_MAIN_OBJS=
54305431

54315432
AS_CASE([$ac_sys_system],
54325433
[Emscripten], [
54335434
AS_VAR_APPEND([PLATFORM_OBJS], [' Python/emscripten_signal.o Python/emscripten_trampoline.o Python/emscripten_trampoline_wasm.o'])
5435+
AS_VAR_APPEND([PLATFORM_MAIN_OBJS], [' Programs/emscripten_beforemain.o'])
54345436
AS_VAR_IF([enable_emscripten_syscalls], [yes], [
54355437
AS_VAR_APPEND([PLATFORM_OBJS], [' Python/emscripten_syscalls.o'])
54365438
])
@@ -5439,6 +5441,7 @@ AS_CASE([$ac_sys_system],
54395441
)
54405442
AC_SUBST([PLATFORM_HEADERS])
54415443
AC_SUBST([PLATFORM_OBJS])
5444+
AC_SUBST([PLATFORM_MAIN_OBJS])
54425445

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

0 commit comments

Comments
 (0)