22
33#include <emscripten.h> // EM_JS, EM_JS_DEPS
44#include <Python.h>
5+ #include "pycore_runtime.h" // _PyRuntime
56
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
7+ // We use the _PyRuntime.emscripten_trampoline field to store a function pointer
8+ // for a wasm-gc based trampoline if it works. Otherwise fall back to JS
9+ // trampoline. The JS trampoline breaks stack switching but every runtime that
10+ // supports stack switching also supports wasm-gc.
11+ //
12+ // We'd like to make the trampoline call into a direct call but currently we
13+ // need to import the wasmTable to compile trampolineModule. emcc >= 4.0.19
14+ // defines the table in WebAssembly and exports it so we won't have access to it
15+ // until after the main module is compiled.
16+ //
17+ // To fix this, one natural solution would be to pass a funcref to the
18+ // trampoline instead of a table index. Several PRs would be needed to fix
19+ // things in llvm and emscripten in order to make this possible.
20+ //
21+ // The performance costs of an extra call_indirect aren't that large anyways.
22+ // The JIT should notice that the target is always the same and turn into a
23+ // check
24+ //
25+ // if (call_target != expected) deoptimize;
26+ // direct_call(call_target, args);
27+
28+ // Offset of emscripten_trampoline in _PyRuntimeState. There's a couple of
29+ // alternatives:
30+ //
31+ // 1. Just make emscripten_trampoline a real C global variable instead of a
32+ // field of _PyRuntimeState. This would violate our rule against mutable
33+ // globals.
34+ //
35+ // 2. #define a preprocessor constant equal to a hard coded number and make a
36+ // _Static_assert(offsetof(_PyRuntimeState, emscripten_trampoline) == OURCONSTANT)
37+ // This has the disadvantage that we have to update the hard coded constant
38+ // when _PyRuntimeState changes
39+ //
40+ // So putting the mutable constant in _PyRuntime and using a immutable global to
41+ // record the offset so we can access it from JS is probably the best way.
42+ EMSCRIPTEN_KEEPALIVE const int _PyEM_EMSCRIPTEN_TRAMPOLINE_OFFSET = offsetof(_PyRuntimeState , emscripten_trampoline );
43+
44+ typedef PyObject * (* TrampolineFunc )(int * success ,
45+ PyCFunctionWithKeywords func ,
46+ PyObject * self ,
47+ PyObject * args ,
48+ PyObject * kw );
49+
50+ /**
51+ * Backwards compatible trampoline works with all JS runtimes
52+ */
53+ EM_JS (PyObject * , _PyEM_TrampolineCall_JS , (PyCFunctionWithKeywords func , PyObject * arg1 , PyObject * arg2 , PyObject * arg3 ), {
1454 return wasmTable .get (func )(arg1 , arg2 , arg3 );
1555}
16- // Try to replace the JS definition of _PyEM_TrampolineCall_inner with a wasm
17- // version.
18- (function () {
56+ // Try to compile wasm-gc trampoline if possible.
57+ function getPyEMTrampolinePtr () {
1958 // Starting with iOS 18.3.1, WebKit on iOS has an issue with the garbage
2059 // collector that breaks the call trampoline. See #130418 and
2160 // https://bugs.webkit.org/show_bug.cgi?id=293113 for details.
@@ -27,19 +66,32 @@ _PyEM_TrampolineCall_inner, (int* success,
2766 (navigator .platform == = 'MacIntel' && typeof navigator .maxTouchPoints != = 'undefined' && navigator .maxTouchPoints > 1 )
2867 );
2968 if (isIOS ) {
30- return ;
69+ return 0 ;
3170 }
71+ let trampolineModule ;
3272 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 ;
73+ trampolineModule = getWasmTrampolineModule ();
3874 } catch (e ) {
3975 // Compilation error due to missing wasm-gc support, fall back to JS
4076 // trampoline
77+ return 0 ;
4178 }
42- })();
79+ const trampolineInstance = new WebAssembly .Instance (trampolineModule , {
80+ env : { __indirect_function_table : wasmTable , memory : wasmMemory },
81+ });
82+ return addFunction (trampolineInstance .exports .trampoline_call );
83+ }
84+ // We have to be careful to work correctly with memory snapshots -- the value of
85+ // _PyRuntimeState.emscripten_trampoline needs to reflect whether wasm-gc is
86+ // available in the current runtime, not in the runtime the snapshot was taken
87+ // in. This writes the appropriate value to
88+ // _PyRuntimeState.emscripten_trampoline from JS startup code that runs every
89+ // time, whether we are restoring a snapshot or not.
90+ addOnPreRun (function setEmscriptenTrampoline () {
91+ const ptr = getPyEMTrampolinePtr ();
92+ const offset = HEAP32 [__PyEM_EMSCRIPTEN_TRAMPOLINE_OFFSET / 4 ];
93+ HEAP32 [(__PyRuntime + offset ) / 4 ] = ptr ;
94+ });
4395);
4496
4597PyObject *
@@ -48,12 +100,19 @@ _PyEM_TrampolineCall(PyCFunctionWithKeywords func,
48100 PyObject * args ,
49101 PyObject * kw )
50102{
103+ TrampolineFunc trampoline = _PyRuntime .emscripten_trampoline ;
104+ if (trampoline == 0 ) {
105+ return _PyEM_TrampolineCall_JS (func , self , args , kw );
106+ }
51107 int success = 1 ;
52- PyObject * result = _PyEM_TrampolineCall_inner (& success , func , self , args , kw );
108+ PyObject * result = trampoline (& success , func , self , args , kw );
53109 if (!success ) {
54110 PyErr_SetString (PyExc_SystemError , "Handler takes too many arguments" );
55111 }
56112 return result ;
57113}
58114
115+ #else
116+ // This is exported so we need to define it even when it isn't used
117+ __attribute__((used )) const int _PyEM_EMSCRIPTEN_TRAMPOLINE_OFFSET = 0 ;
59118#endif
0 commit comments