1+ #include <emscripten.h> // EM_JS, EM_JS_DEPS
12#if defined(PY_CALL_TRAMPOLINE )
23
3- #include <emscripten.h> // EM_JS, EM_JS_DEPS
44#include <Python.h>
55
6- EM_JS (
7- PyObject * ,
8- _PyEM_TrampolineCall_inner , (int * success ,
9- PyCFunctionWithKeywords func ,
10- PyObject * arg1 ,
11- PyObject * arg2 ,
12- PyObject * arg3 ), {
13- // JavaScript fallback trampoline
6+ // We use the _PyRuntime.emscripten_trampoline field to store a function pointer
7+ // for a wasm-gc based trampoline if it works. Otherwise fall back to JS
8+ // trampoline. The JS trampoline breaks stack switching but every runtime that
9+ // supports stack switching also supports wasm-gc.
10+ //
11+ // We'd like to make the trampoline call into a direct call but currently we
12+ // need to import the wasmTable to compile trampolineModule. emcc >= 4.0.19
13+ // defines the table in WebAssembly and exports it so we won't have access to it
14+ // until after the main module is compiled.
15+ //
16+ // To fix this, one natural solution would be to pass a funcref to the
17+ // trampoline instead of a table index. Several PRs would be needed to fix
18+ // things in llvm and emscripten in order to make this possible.
19+ //
20+ // The performance costs of an extra call_indirect aren't that large anyways.
21+ // The JIT should notice that the target is always the same and turn into a
22+ // check
23+ //
24+ // if (call_target != expected) deoptimize;
25+ // direct_call(call_target, args);
26+
27+ // Offset of emscripten_trampoline in _PyRuntimeState. There's a couple of
28+ // alternatives:
29+ //
30+ // 1. Just make emscripten_trampoline a real C global variable instead of a
31+ // field of _PyRuntimeState. This would violate our rule against mutable
32+ // globals.
33+ //
34+ // 2. #define a preprocessor constant equal to a hard coded number and make a
35+ // _Static_assert(offsetof(_PyRuntimeState, emscripten_trampoline) == OURCONSTANT)
36+ // This has the disadvantage that we have to update the hard coded constant
37+ // when _PyRuntimeState changes
38+ //
39+ // So putting the mutable constant in _PyRuntime and using a immutable global to
40+ // record the offset so we can access it from JS is probably the best way.
41+ EMSCRIPTEN_KEEPALIVE const int _PyEM_EMSCRIPTEN_TRAMPOLINE_OFFSET = offsetof(_PyRuntimeState , emscripten_trampoline );
42+
43+ typedef PyObject * (* TrampolineFunc )(int * success ,
44+ void * func ,
45+ PyObject * self ,
46+ PyObject * args ,
47+ PyObject * kw );
48+
49+ /**
50+ * Backwards compatible trampoline works with all JS runtimes
51+ */
52+ EM_JS (PyObject * , _PyEM_TrampolineCall_JS , (PyCFunctionWithKeywords func , PyObject * arg1 , PyObject * arg2 , PyObject * arg3 ), {
1453 return wasmTable .get (func )(arg1 , arg2 , arg3 );
1554}
16- // Try to replace the JS definition of _PyEM_TrampolineCall_inner with a wasm
17- // version.
18- (function () {
55+ // Try to compile wasm-gc trampoline if possible.
56+ function getPyEMTrampolinePtr () {
1957 // Starting with iOS 18.3.1, WebKit on iOS has an issue with the garbage
2058 // collector that breaks the call trampoline. See #130418 and
2159 // https://bugs.webkit.org/show_bug.cgi?id=293113 for details.
@@ -27,19 +65,32 @@ _PyEM_TrampolineCall_inner, (int* success,
2765 (navigator .platform == = 'MacIntel' && typeof navigator .maxTouchPoints != = 'undefined' && navigator .maxTouchPoints > 1 )
2866 );
2967 if (isIOS ) {
30- return ;
68+ return 0 ;
3169 }
70+ let trampolineModule ;
3271 try {
33- const trampolineModule = getWasmTrampolineModule ();
34- const trampolineInstance = new WebAssembly .Instance (trampolineModule , {
35- env : { __indirect_function_table : wasmTable , memory : wasmMemory },
36- });
37- _PyEM_TrampolineCall_inner = trampolineInstance .exports .trampoline_call ;
72+ trampolineModule = getWasmTrampolineModule ();
3873 } catch (e ) {
3974 // Compilation error due to missing wasm-gc support, fall back to JS
4075 // trampoline
76+ return 0 ;
4177 }
42- })();
78+ const trampolineInstance = new WebAssembly .Instance (trampolineModule , {
79+ env : { __indirect_function_table : wasmTable , memory : wasmMemory },
80+ });
81+ return addFunction (trampolineInstance .exports .trampoline_call );
82+ }
83+ // We have to be careful to work correctly with memory snapshots -- the value of
84+ // _PyRuntimeState.emscripten_trampoline needs to reflect whether wasm-gc is
85+ // available in the current runtime, not in the runtime the snapshot was taken
86+ // in. This writes the appropriate value to
87+ // _PyRuntimeState.emscripten_trampoline from JS startup code that runs every
88+ // time, whether we are restoring a snapshot or not.
89+ addOnPreRun (function setEmscriptenTrampoline () {
90+ const ptr = getPyEMTrampolinePtr ();
91+ const offset = HEAP32 [__PyEM_EMSCRIPTEN_TRAMPOLINE_OFFSET / 4 ];
92+ HEAP32 [(__PyRuntime + offset ) / 4 ] = ptr ;
93+ });
4394);
4495
4596PyObject *
@@ -48,12 +99,18 @@ _PyEM_TrampolineCall(PyCFunctionWithKeywords func,
4899 PyObject * args ,
49100 PyObject * kw )
50101{
51- int success = 1 ;
52- PyObject * result = _PyEM_TrampolineCall_inner (& success , func , self , args , kw );
102+ TrampolineFunc trampoline = _PyRuntime .emscripten_trampoline ;
103+ if (trampoline == 0 ) {
104+ return _PyEM_TrampolineCall_JS (func , self , args , kw );
105+ }
106+ PyObject * result = trampoline (& success , func , self , args , kw );
53107 if (!success ) {
54108 PyErr_SetString (PyExc_SystemError , "Handler takes too many arguments" );
55109 }
56110 return result ;
57111}
58112
113+ #else
114+ // This is exported so we need to define it even when it isn't used
115+ EMSCRIPTEN_KEEPALIVE const int _PyEM_EMSCRIPTEN_TRAMPOLINE_OFFSET = 0 ;
59116#endif
0 commit comments