Skip to content

Commit 6bbee1e

Browse files
committed
gh-128627: Use __builtin_wasm_test_function_pointer_signature for Emscripten trampolines
Since llvm/llvm-project#150201 was merged, there is now a better way to do this. Requires Emscripten 4.0.12.
1 parent 481d5b5 commit 6bbee1e

File tree

5 files changed

+68
-162
lines changed

5 files changed

+68
-162
lines changed

Include/internal/pycore_runtime_structs.h

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -279,7 +279,10 @@ struct pyruntimestate {
279279
#if defined(__EMSCRIPTEN__) && defined(PY_CALL_TRAMPOLINE)
280280
// Used in "Python/emscripten_trampoline.c" to choose between type
281281
// reflection trampoline and EM_JS trampoline.
282-
int (*emscripten_count_args_function)(PyCFunctionWithKeywords func);
282+
PyObject *(*emscripten_trampoline)(int *success,
283+
PyCFunctionWithKeywords func,
284+
PyObject *arg1, PyObject *arg2,
285+
PyObject *arg3);
283286
#endif
284287

285288
/* All the objects that are shared by the runtime's interpreters. */

Makefile.pre.in

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3106,6 +3106,11 @@ config.status: $(srcdir)/configure
31063106
Python/asm_trampoline.o: $(srcdir)/Python/asm_trampoline.S
31073107
$(CC) -c $(PY_CORE_CFLAGS) -o $@ $<
31083108

3109+
Python/emscripten_trampoline_inner.wasm: $(srcdir)/Python/emscripten_trampoline_inner.c
3110+
$$(dirname $$(dirname $(CC)))/bin/clang -o $@ $< -mgc -O2 -Wl,--no-entry -Wl,--import-table -Wl,--import-memory -target wasm32-unknown-unknown -nostdlib
3111+
3112+
Python/emscripten_trampoline.o: $(srcdir)/Python/emscripten_trampoline.c Python/emscripten_trampoline_inner.wasm
3113+
$(CC) -c $(PY_CORE_CFLAGS) -o $@ $<
31093114

31103115
JIT_DEPS = \
31113116
$(srcdir)/Tools/jit/*.c \

Python/emscripten_trampoline.c

Lines changed: 57 additions & 159 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,16 @@
44
#include <Python.h>
55
#include "pycore_runtime.h" // _PyRuntime
66

7-
typedef int (*CountArgsFunc)(PyCFunctionWithKeywords func);
7+
typedef PyObject *(*TrampolineFunc)(int *success, PyCFunctionWithKeywords func,
8+
PyObject *arg1, PyObject *arg2,
9+
PyObject *arg3);
10+
11+
EMSCRIPTEN_KEEPALIVE const char trampoline_inner_wasm[] = {
12+
#embed "Python/emscripten_trampoline_inner.wasm"
13+
};
14+
15+
EMSCRIPTEN_KEEPALIVE int trampoline_inner_wasm_length =
16+
sizeof(trampoline_inner_wasm);
817

918
// Offset of emscripten_count_args_function in _PyRuntimeState. There's a couple
1019
// of alternatives:
@@ -18,159 +27,55 @@ typedef int (*CountArgsFunc)(PyCFunctionWithKeywords func);
1827
//
1928
// So putting the mutable constant in _PyRuntime and using a immutable global to
2029
// record the offset so we can access it from JS is probably the best way.
21-
EMSCRIPTEN_KEEPALIVE const int _PyEM_EMSCRIPTEN_COUNT_ARGS_OFFSET = offsetof(_PyRuntimeState, emscripten_count_args_function);
22-
23-
EM_JS(CountArgsFunc, _PyEM_GetCountArgsPtr, (), {
24-
return Module._PyEM_CountArgsPtr; // initialized below
25-
}
26-
// Binary module for the checks. It has to be done in web assembly because
27-
// clang/llvm have no support yet for the reference types yet. In fact, the wasm
28-
// binary toolkit doesn't yet support the ref.test instruction either. To
29-
// convert the following textual wasm to a binary, you can build wabt from this
30-
// branch: https://github.com/WebAssembly/wabt/pull/2529 and then use that
31-
// wat2wasm binary.
32-
//
33-
// (module
34-
// (type $type0 (func (param) (result i32)))
35-
// (type $type1 (func (param i32) (result i32)))
36-
// (type $type2 (func (param i32 i32) (result i32)))
37-
// (type $type3 (func (param i32 i32 i32) (result i32)))
38-
// (type $blocktype (func (param) (result)))
39-
// (table $funcs (import "e" "t") 0 funcref)
40-
// (export "f" (func $f))
41-
// (func $f (param $fptr i32) (result i32)
42-
// (local $fref funcref)
43-
// local.get $fptr
44-
// table.get $funcs
45-
// local.tee $fref
46-
// ref.test $type3
47-
// if $blocktype
48-
// i32.const 3
49-
// return
50-
// end
51-
// local.get $fref
52-
// ref.test $type2
53-
// if $blocktype
54-
// i32.const 2
55-
// return
56-
// end
57-
// local.get $fref
58-
// ref.test $type1
59-
// if $blocktype
60-
// i32.const 1
61-
// return
62-
// end
63-
// local.get $fref
64-
// ref.test $type0
65-
// if $blocktype
66-
// i32.const 0
67-
// return
68-
// end
69-
// i32.const -1
70-
// )
71-
// )
72-
73-
function getPyEMCountArgsPtr() {
74-
// Starting with iOS 18.3.1, WebKit on iOS has an issue with the garbage
75-
// collector that breaks the call trampoline. See #130418 and
76-
// https://bugs.webkit.org/show_bug.cgi?id=293113 for details.
77-
let isIOS = globalThis.navigator && (
78-
/iPad|iPhone|iPod/.test(navigator.userAgent) ||
79-
// Starting with iPadOS 13, iPads might send a platform string that looks like a desktop Mac.
80-
// To differentiate, we check if the platform is 'MacIntel' (common for Macs and newer iPads)
81-
// AND if the device has multi-touch capabilities (navigator.maxTouchPoints > 1)
82-
(navigator.platform === 'MacIntel' && typeof navigator.maxTouchPoints !== 'undefined' && navigator.maxTouchPoints > 1)
83-
);
84-
if (isIOS) {
30+
EMSCRIPTEN_KEEPALIVE const int _PyEM_EMSCRIPTEN_TRAMPOLINE_OFFSET =
31+
offsetof(_PyRuntimeState, emscripten_trampoline);
32+
33+
EM_JS(
34+
TrampolineFunc, _PyEM_GetTrampolinePtr, (void),
35+
{
36+
return Module._PyEM_CountArgsPtr; // initialized below
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
44+
// string that looks like a desktop Mac. To differentiate, we
45+
// check if the platform is 'MacIntel' (common for Macs and
46+
// newer iPads) AND if the device has multi-touch
47+
// capabilities (navigator.maxTouchPoints > 1)
48+
(navigator.platform ==
49+
= 'MacIntel' &&typeof navigator.maxTouchPoints !=
50+
= 'undefined' && navigator.maxTouchPoints > 1));
51+
if (isIOS) {
8552
return 0;
86-
}
87-
88-
// Try to initialize countArgsFunc
89-
const code = new Uint8Array([
90-
0x00, 0x61, 0x73, 0x6d, // \0asm magic number
91-
0x01, 0x00, 0x00, 0x00, // version 1
92-
0x01, 0x1a, // Type section, body is 0x1a bytes
93-
0x05, // 6 entries
94-
0x60, 0x00, 0x01, 0x7f, // (type $type0 (func (param) (result i32)))
95-
0x60, 0x01, 0x7f, 0x01, 0x7f, // (type $type1 (func (param i32) (result i32)))
96-
0x60, 0x02, 0x7f, 0x7f, 0x01, 0x7f, // (type $type2 (func (param i32 i32) (result i32)))
97-
0x60, 0x03, 0x7f, 0x7f, 0x7f, 0x01, 0x7f, // (type $type3 (func (param i32 i32 i32) (result i32)))
98-
0x60, 0x00, 0x00, // (type $blocktype (func (param) (result)))
99-
0x02, 0x09, // Import section, 0x9 byte body
100-
0x01, // 1 import (table $funcs (import "e" "t") 0 funcref)
101-
0x01, 0x65, // "e"
102-
0x01, 0x74, // "t"
103-
0x01, // importing a table
104-
0x70, // of entry type funcref
105-
0x00, 0x00, // table limits: no max, min of 0
106-
0x03, 0x02, // Function section
107-
0x01, 0x01, // We're going to define one function of type 1 (func (param i32) (result i32))
108-
0x07, 0x05, // export section
109-
0x01, // 1 export
110-
0x01, 0x66, // called "f"
111-
0x00, // a function
112-
0x00, // at index 0
113-
114-
0x0a, 56, // Code section,
115-
0x01, 54, // one entry of length 54
116-
0x01, 0x01, 0x70, // one local of type funcref
117-
// Body of the function
118-
0x20, 0x00, // local.get $fptr
119-
0x25, 0x00, // table.get $funcs
120-
0x22, 0x01, // local.tee $fref
121-
0xfb, 0x14, 0x03, // ref.test $type3
122-
0x04, 0x04, // if (type $blocktype)
123-
0x41, 0x03, // i32.const 3
124-
0x0f, // return
125-
0x0b, // end block
126-
127-
0x20, 0x01, // local.get $fref
128-
0xfb, 0x14, 0x02, // ref.test $type2
129-
0x04, 0x04, // if (type $blocktype)
130-
0x41, 0x02, // i32.const 2
131-
0x0f, // return
132-
0x0b, // end block
133-
134-
0x20, 0x01, // local.get $fref
135-
0xfb, 0x14, 0x01, // ref.test $type1
136-
0x04, 0x04, // if (type $blocktype)
137-
0x41, 0x01, // i32.const 1
138-
0x0f, // return
139-
0x0b, // end block
140-
141-
0x20, 0x01, // local.get $fref
142-
0xfb, 0x14, 0x00, // ref.test $type0
143-
0x04, 0x04, // if (type $blocktype)
144-
0x41, 0x00, // i32.const 0
145-
0x0f, // return
146-
0x0b, // end block
147-
148-
0x41, 0x7f, // i32.const -1
149-
0x0b // end function
150-
]);
151-
try {
53+
}
54+
const code = HEAP8.subarray(
55+
_trampoline_inner_wasm,
56+
_trampoline_inner_wasm + HEAP32[_trampoline_inner_wasm_length / 4]);
57+
try {
15258
const mod = new WebAssembly.Module(code);
15359
const inst = new WebAssembly.Instance(mod, { e: { t: wasmTable } });
154-
return addFunction(inst.exports.f);
155-
} catch (e) {
60+
return addFunction(inst.exports.trampoline_call);
61+
} catch (e) {
15662
// If something goes wrong, we'll null out _PyEM_CountFuncParams and fall
15763
// back to the JS trampoline.
15864
return 0;
65+
}
15966
}
160-
}
16167

162-
addOnPreRun(() => {
163-
const ptr = getPyEMCountArgsPtr();
164-
Module._PyEM_CountArgsPtr = ptr;
165-
const offset = HEAP32[__PyEM_EMSCRIPTEN_COUNT_ARGS_OFFSET / 4];
166-
HEAP32[(__PyRuntime + offset) / 4] = ptr;
167-
});
168-
);
68+
addOnPreRun(() = > {
69+
const ptr = getPyEMTrampolinePtr();
70+
Module._PyEM_CountArgsPtr = ptr;
71+
const offset = HEAP32[__PyEM_EMSCRIPTEN_TRAMPOLINE_OFFSET / 4];
72+
HEAP32[(__PyRuntime + offset) / 4] = ptr;
73+
}););
16974

17075
void
17176
_Py_EmscriptenTrampoline_Init(_PyRuntimeState *runtime)
17277
{
173-
runtime->emscripten_count_args_function = _PyEM_GetCountArgsPtr();
78+
runtime->emscripten_trampoline = _PyEM_GetTrampolinePtr();
17479
}
17580

17681
// We have to be careful to work correctly with memory snapshots. Even if we are
@@ -196,23 +101,16 @@ _PyEM_TrampolineCall(PyCFunctionWithKeywords func,
196101
PyObject* args,
197102
PyObject* kw)
198103
{
199-
CountArgsFunc count_args = _PyRuntime.emscripten_count_args_function;
200-
if (count_args == 0) {
201-
return _PyEM_TrampolineCall_JS(func, self, args, kw);
202-
}
203-
switch (count_args(func)) {
204-
case 0:
205-
return ((zero_arg)func)();
206-
case 1:
207-
return ((one_arg)func)(self);
208-
case 2:
209-
return ((two_arg)func)(self, args);
210-
case 3:
211-
return ((three_arg)func)(self, args, kw);
212-
default:
213-
PyErr_SetString(PyExc_SystemError, "Handler takes too many arguments");
214-
return NULL;
215-
}
104+
TrampolineFunc trampoline = _PyRuntime.emscripten_trampoline;
105+
if (trampoline == 0) {
106+
return _PyEM_TrampolineCall_JS(func, self, args, kw);
107+
}
108+
int success = 0;
109+
PyObject *result = trampoline(&success, func, self, args, kw);
110+
if (!success) {
111+
PyErr_SetString(PyExc_SystemError, "Handler takes too many arguments");
112+
}
113+
return result;
216114
}
217115

218116
#endif

configure

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

configure.ac

Lines changed: 1 addition & 1 deletion
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_COUNT_ARGS_OFFSET,_PyGILState_GetThisThreadState,__Py_DumpTraceback"])
2339+
AS_VAR_APPEND([LINKFORSHARED], [" -sEXPORTED_FUNCTIONS=_main,_Py_Version,__PyRuntime,__PyEM_EMSCRIPTEN_TRAMPOLINE_OFFSET,_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"])

0 commit comments

Comments
 (0)