Skip to content

Commit 0ffb76b

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 0ffb76b

File tree

5 files changed

+39
-135
lines changed

5 files changed

+39
-135
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: 28 additions & 132 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,59 +27,14 @@ 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);
30+
EMSCRIPTEN_KEEPALIVE const int _PyEM_EMSCRIPTEN_TRAMPOLINE_OFFSET =
31+
offsetof(_PyRuntimeState, emscripten_trampoline);
2232

23-
EM_JS(CountArgsFunc, _PyEM_GetCountArgsPtr, (), {
33+
EM_JS(TrampolineFunc, _PyEM_GetTrampolinePtr, (void), {
2434
return Module._PyEM_CountArgsPtr; // initialized below
2535
}
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-
// )
7236

73-
function getPyEMCountArgsPtr() {
37+
function getPyEMTrampolinePtr() {
7438
// Starting with iOS 18.3.1, WebKit on iOS has an issue with the garbage
7539
// collector that breaks the call trampoline. See #130418 and
7640
// https://bugs.webkit.org/show_bug.cgi?id=293113 for details.
@@ -84,74 +48,13 @@ function getPyEMCountArgsPtr() {
8448
if (isIOS) {
8549
return 0;
8650
}
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-
]);
51+
const code = HEAP8.subarray(
52+
_trampoline_inner_wasm,
53+
_trampoline_inner_wasm + HEAP32[_trampoline_inner_wasm_length / 4]);
15154
try {
15255
const mod = new WebAssembly.Module(code);
15356
const inst = new WebAssembly.Instance(mod, { e: { t: wasmTable } });
154-
return addFunction(inst.exports.f);
57+
return addFunction(inst.exports.trampoline_call);
15558
} catch (e) {
15659
// If something goes wrong, we'll null out _PyEM_CountFuncParams and fall
15760
// back to the JS trampoline.
@@ -160,17 +63,17 @@ function getPyEMCountArgsPtr() {
16063
}
16164

16265
addOnPreRun(() => {
163-
const ptr = getPyEMCountArgsPtr();
66+
const ptr = getPyEMTrampolinePtr();
16467
Module._PyEM_CountArgsPtr = ptr;
165-
const offset = HEAP32[__PyEM_EMSCRIPTEN_COUNT_ARGS_OFFSET / 4];
68+
const offset = HEAP32[__PyEM_EMSCRIPTEN_TRAMPOLINE_OFFSET / 4];
16669
HEAP32[(__PyRuntime + offset) / 4] = ptr;
16770
});
16871
);
16972

17073
void
17174
_Py_EmscriptenTrampoline_Init(_PyRuntimeState *runtime)
17275
{
173-
runtime->emscripten_count_args_function = _PyEM_GetCountArgsPtr();
76+
runtime->emscripten_trampoline = _PyEM_GetTrampolinePtr();
17477
}
17578

17679
// We have to be careful to work correctly with memory snapshots. Even if we are
@@ -196,23 +99,16 @@ _PyEM_TrampolineCall(PyCFunctionWithKeywords func,
19699
PyObject* args,
197100
PyObject* kw)
198101
{
199-
CountArgsFunc count_args = _PyRuntime.emscripten_count_args_function;
200-
if (count_args == 0) {
102+
TrampolineFunc trampoline = _PyRuntime.emscripten_trampoline;
103+
if (trampoline == 0) {
201104
return _PyEM_TrampolineCall_JS(func, self, args, kw);
202105
}
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;
106+
int success = 0;
107+
PyObject *result = trampoline(&success, func, self, args, kw);
108+
if (!success) {
109+
PyErr_SetString(PyExc_SystemError, "Handler takes too many arguments");
215110
}
111+
return result;
216112
}
217113

218114
#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)