Skip to content

Commit e96892e

Browse files
committed
New approach
1 parent 6fd7bfe commit e96892e

File tree

5 files changed

+93
-75
lines changed

5 files changed

+93
-75
lines changed

Makefile.pre.in

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3110,7 +3110,13 @@ Python/emscripten_trampoline_inner.wasm: $(srcdir)/Python/emscripten_trampoline_
31103110
# emcc has a path that ends with emsdk/upstream/emscripten/emcc, we're looking for emsdk/upstream/bin/clang.
31113111
$$(dirname $$(dirname $(CC)))/bin/clang -o $@ $< -mgc -O2 -Wl,--no-entry -Wl,--import-table -Wl,--import-memory -target wasm32-unknown-unknown -nostdlib
31123112

3113-
Python/emscripten_trampoline.o: $(srcdir)/Python/emscripten_trampoline.c Python/emscripten_trampoline_inner.wasm
3113+
Python/emscripten_trampoline_wasm.c: Python/emscripten_trampoline_inner.wasm
3114+
$(PYTHON_FOR_REGEN) $(srcdir)/Tools/wasm/emscripten/prepare_wat.py $< $@ getWasmTrampolineModule
3115+
3116+
Python/emscripten_trampoline_wasm.o: Python/emscripten_trampoline_wasm.c
3117+
$(CC) -c $(PY_CORE_CFLAGS) -o $@ $<
3118+
3119+
Python/emscripten_trampoline.o: $(srcdir)/Python/emscripten_trampoline.c
31143120
$(CC) -c $(PY_CORE_CFLAGS) -o $@ $<
31153121

31163122
JIT_DEPS = \

Python/emscripten_trampoline.c

Lines changed: 12 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -8,72 +8,10 @@ typedef PyObject *(*TrampolineFunc)(int *success, PyCFunctionWithKeywords func,
88
PyObject *arg1, PyObject *arg2,
99
PyObject *arg3);
1010

11-
EMSCRIPTEN_KEEPALIVE const char _PyEM_trampoline_inner_wasm[] = {
12-
#embed "Python/emscripten_trampoline_inner.wasm"
13-
};
14-
15-
EMSCRIPTEN_KEEPALIVE const int _PyEM_trampoline_inner_wasm_length =
16-
sizeof(trampoline_inner_wasm);
17-
18-
// Offset of emscripten_count_args_function in _PyRuntimeState. There's a couple
19-
// of alternatives:
20-
// 1. Just make emscripten_count_args_function a real C global variable instead
21-
// of a field of _PyRuntimeState. This would violate our rule against mutable
22-
// globals.
23-
// 2. #define a preprocessor constant equal to a hard coded number and make a
24-
// _Static_assert(offsetof(_PyRuntimeState, emscripten_count_args_function)
25-
// == OURCONSTANT) This has the disadvantage that we have to update the hard
26-
// coded constant when _PyRuntimeState changes
27-
//
28-
// So putting the mutable constant in _PyRuntime and using a immutable global to
29-
// record the offset so we can access it from JS is probably the best way.
30-
EMSCRIPTEN_KEEPALIVE const int _PyEM_EMSCRIPTEN_TRAMPOLINE_OFFSET =
31-
offsetof(_PyRuntimeState, emscripten_trampoline);
32-
33-
EM_JS(TrampolineFunc, _PyEM_GetTrampolinePtr, (void), {
34-
return Module._PyEM_CountArgsPtr; // initialized below
35-
}
36-
37-
function getPyEMTrampolinePtr() {
38-
// Starting with iOS 18.3.1, WebKit on iOS has an issue with the garbage
39-
// collector that breaks the call trampoline. See #130418 and
40-
// https://bugs.webkit.org/show_bug.cgi?id=293113 for details.
41-
let isIOS = globalThis.navigator && (
42-
/iPad|iPhone|iPod/.test(navigator.userAgent) ||
43-
// Starting with iPadOS 13, iPads might send a platform string that looks like a desktop Mac.
44-
// To differentiate, we check if the platform is 'MacIntel' (common for Macs and newer iPads)
45-
// AND if the device has multi-touch capabilities (navigator.maxTouchPoints > 1)
46-
(navigator.platform === 'MacIntel' && typeof navigator.maxTouchPoints !== 'undefined' && navigator.maxTouchPoints > 1)
47-
);
48-
if (isIOS) {
49-
return 0;
50-
}
51-
const code = HEAP8.subarray(
52-
__PyEM_trampoline_inner_wasm,
53-
__PyEM_trampoline_inner_wasm + HEAP32[__PyEM_trampoline_inner_wasm_length / 4]);
54-
try {
55-
const mod = new WebAssembly.Module(code);
56-
const inst = new WebAssembly.Instance(mod, { e: { t: wasmTable } });
57-
return addFunction(inst.exports.trampoline_call);
58-
} catch (e) {
59-
// If something goes wrong, we'll null out _PyEM_CountFuncParams and fall
60-
// back to the JS trampoline.
61-
return 0;
62-
}
63-
}
64-
65-
addOnPreRun(() => {
66-
const ptr = getPyEMTrampolinePtr();
67-
Module._PyEM_CountArgsPtr = ptr;
68-
const offset = HEAP32[__PyEM_EMSCRIPTEN_TRAMPOLINE_OFFSET / 4];
69-
HEAP32[(__PyRuntime + offset) / 4] = ptr;
70-
});
71-
);
7211

7312
void
7413
_Py_EmscriptenTrampoline_Init(_PyRuntimeState *runtime)
7514
{
76-
runtime->emscripten_trampoline = _PyEM_GetTrampolinePtr();
7715
}
7816

7917
// We have to be careful to work correctly with memory snapshots. Even if we are
@@ -84,9 +22,17 @@ _Py_EmscriptenTrampoline_Init(_PyRuntimeState *runtime)
8422
/**
8523
* Backwards compatible trampoline works with all JS runtimes
8624
*/
87-
EM_JS(PyObject*, _PyEM_TrampolineCall_JS, (PyCFunctionWithKeywords func, PyObject *arg1, PyObject *arg2, PyObject *arg3), {
25+
EM_JS(PyObject*, _PyEM_TrampolineCall_inner, (int* success, PyCFunctionWithKeywords func, PyObject *arg1, PyObject *arg2, PyObject *arg3), {
8826
return wasmTable.get(func)(arg1, arg2, arg3);
89-
});
27+
}
28+
try {
29+
const trampolineModule = getWasmTrampolineModule();
30+
const trampolineInstance = new WebAssembly.Instance(trampolineModule, {
31+
env: { __indirect_function_table: wasmTable, memory: wasmMemory },
32+
});
33+
_PyEM_TrampolineCall_inner = trampolineInstance.exports.trampoline_call;
34+
} catch (e) {}
35+
);
9036

9137
typedef PyObject* (*zero_arg)(void);
9238
typedef PyObject* (*one_arg)(PyObject*);
@@ -99,12 +45,8 @@ _PyEM_TrampolineCall(PyCFunctionWithKeywords func,
9945
PyObject* args,
10046
PyObject* kw)
10147
{
102-
TrampolineFunc trampoline = _PyRuntime.emscripten_trampoline;
103-
if (trampoline == 0) {
104-
return _PyEM_TrampolineCall_JS(func, self, args, kw);
105-
}
106-
int success = 0;
107-
PyObject *result = trampoline(&success, func, self, args, kw);
48+
int success = 1;
49+
PyObject *result = _PyEM_TrampolineCall_inner(&success, func, self, args, kw);
10850
if (!success) {
10951
PyErr_SetString(PyExc_SystemError, "Handler takes too many arguments");
11052
}
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
#!/usr/bin/env python3
2+
3+
import argparse
4+
import sys
5+
from pathlib import Path
6+
from shutil import which
7+
8+
JS_TEMPLATE = """
9+
#include "emscripten.h"
10+
11+
EM_JS(void, {function_name}, (void), {{
12+
return new WebAssembly.Module(hexStringToUTF8Array("{hex_string}"));
13+
}}
14+
function hexStringToUTF8Array(hex) {{
15+
const bytes = [];
16+
for (let i = 0; i < hex.length; i += 2) {{
17+
bytes.push(parseInt(hex.substr(i, 2), 16));
18+
}}
19+
return new Uint8Array(bytes);
20+
}});
21+
"""
22+
23+
24+
def find_wasm_as():
25+
emcc_path = which("emcc")
26+
if not emcc_path:
27+
print("Error: emcc not found in PATH", file=sys.stderr)
28+
return None
29+
30+
wasm_as_path = Path(emcc_path).parents[1] / "bin/wasm-as"
31+
32+
if not wasm_as_path.exists():
33+
print(f"Error: wasm-as not found at {wasm_as_path}", file=sys.stderr)
34+
return None
35+
return wasm_as_path
36+
37+
38+
def compile_wat(input_file, output_file, function_name):
39+
# Read the compiled WASM as binary and convert to hex
40+
wasm_bytes = Path(input_file).read_bytes()
41+
42+
hex_string = "".join(f"{byte:02x}" for byte in wasm_bytes)
43+
44+
# Generate JavaScript module
45+
js_content = JS_TEMPLATE.format(
46+
function_name=function_name, hex_string=hex_string
47+
)
48+
Path(output_file).write_text(js_content)
49+
50+
print(
51+
f"Successfully compiled {input_file} and generated {output_file}"
52+
)
53+
return 0
54+
55+
56+
def main():
57+
parser = argparse.ArgumentParser(
58+
description="Compile WebAssembly text files using wasm-as"
59+
)
60+
parser.add_argument("input_file", help="Input .wat file to compile")
61+
parser.add_argument("output_file", help="Output file name")
62+
parser.add_argument("function_name", help="Name of the export function")
63+
64+
args = parser.parse_args()
65+
66+
return compile_wat(args.input_file, args.output_file, args.function_name)
67+
68+
69+
if __name__ == "__main__":
70+
sys.exit(main())

configure

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

configure.ac

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2336,7 +2336,7 @@ AS_CASE([$ac_sys_system],
23362336
dnl Include file system support
23372337
AS_VAR_APPEND([LINKFORSHARED], [" -sFORCE_FILESYSTEM -lidbfs.js -lnodefs.js -lproxyfs.js -lworkerfs.js"])
23382338
AS_VAR_APPEND([LINKFORSHARED], [" -sEXPORTED_RUNTIME_METHODS=FS,callMain,ENV,HEAPU32,TTY"])
2339-
AS_VAR_APPEND([LINKFORSHARED], [" -sEXPORTED_FUNCTIONS=_main,_Py_Version,__PyRuntime,__PyEM_EMSCRIPTEN_TRAMPOLINE_OFFSET,_PyGILState_GetThisThreadState,__Py_DumpTraceback"])
2339+
AS_VAR_APPEND([LINKFORSHARED], [" -sEXPORTED_FUNCTIONS=_main,_Py_Version,__PyRuntime,_PyGILState_GetThisThreadState,__Py_DumpTraceback"])
23402340
AS_VAR_APPEND([LINKFORSHARED], [" -sSTACK_SIZE=5MB"])
23412341
dnl Avoid bugs in JS fallback string decoding path
23422342
AS_VAR_APPEND([LINKFORSHARED], [" -sTEXTDECODER=2"])
@@ -5139,7 +5139,7 @@ PLATFORM_OBJS=
51395139

51405140
AS_CASE([$ac_sys_system],
51415141
[Emscripten], [
5142-
AS_VAR_APPEND([PLATFORM_OBJS], [' Python/emscripten_signal.o Python/emscripten_trampoline.o Python/emscripten_syscalls.o'])
5142+
AS_VAR_APPEND([PLATFORM_OBJS], [' Python/emscripten_signal.o Python/emscripten_trampoline.o Python/emscripten_trampoline_wasm.o Python/emscripten_syscalls.o'])
51435143
AS_VAR_APPEND([PLATFORM_HEADERS], [' $(srcdir)/Include/internal/pycore_emscripten_signal.h $(srcdir)/Include/internal/pycore_emscripten_trampoline.h'])
51445144
],
51455145
)

0 commit comments

Comments
 (0)