22
33#include <emscripten.h> // EM_JS, EM_JS_DEPS
44#include <Python.h>
5- #include "pycore_runtime.h" // _PyRuntime
65
7- typedef int (* CountArgsFunc )(PyCFunctionWithKeywords func );
8-
9- // Offset of emscripten_count_args_function in _PyRuntimeState. There's a couple
10- // of alternatives:
11- // 1. Just make emscripten_count_args_function a real C global variable instead
12- // of a field of _PyRuntimeState. This would violate our rule against mutable
13- // globals.
14- // 2. #define a preprocessor constant equal to a hard coded number and make a
15- // _Static_assert(offsetof(_PyRuntimeState, emscripten_count_args_function)
16- // == OURCONSTANT) This has the disadvantage that we have to update the hard
17- // coded constant when _PyRuntimeState changes
18- //
19- // So putting the mutable constant in _PyRuntime and using a immutable global to
20- // 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
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
14+ return wasmTable .get (func )(arg1 , arg2 , arg3 );
2515}
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 () {
16+ // Try to replace the JS definition of _PyEM_TrampolineCall_inner with a wasm
17+ // version.
18+ (function () {
7419 // Starting with iOS 18.3.1, WebKit on iOS has an issue with the garbage
7520 // collector that breaks the call trampoline. See #130418 and
7621 // https://bugs.webkit.org/show_bug.cgi?id=293113 for details.
@@ -82,137 +27,33 @@ function getPyEMCountArgsPtr() {
8227 (navigator .platform == = 'MacIntel' && typeof navigator .maxTouchPoints != = 'undefined' && navigator .maxTouchPoints > 1 )
8328 );
8429 if (isIOS ) {
85- return 0 ;
30+ return ;
8631 }
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- ]);
15132 try {
152- const mod = new WebAssembly .Module (code );
153- const inst = new WebAssembly .Instance (mod , { e : { t : wasmTable } });
154- return addFunction (inst .exports .f );
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 ;
15538 } catch (e ) {
156- // If something goes wrong, we'll null out _PyEM_CountFuncParams and fall
157- // back to the JS trampoline.
158- return 0 ;
39+ // Compilation error due to missing wasm-gc support, fall back to JS
40+ // trampoline
15941 }
160- }
161-
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- });
42+ })();
16843);
16944
170- void
171- _Py_EmscriptenTrampoline_Init (_PyRuntimeState * runtime )
172- {
173- runtime -> emscripten_count_args_function = _PyEM_GetCountArgsPtr ();
174- }
175-
176- // We have to be careful to work correctly with memory snapshots. Even if we are
177- // loading a memory snapshot, we need to perform the JS initialization work.
178- // That means we can't call the initialization code from C. Instead, we export
179- // this function pointer to JS and then fill it in a preRun function which runs
180- // unconditionally.
181- /**
182- * Backwards compatible trampoline works with all JS runtimes
183- */
184- EM_JS (PyObject * , _PyEM_TrampolineCall_JS , (PyCFunctionWithKeywords func , PyObject * arg1 , PyObject * arg2 , PyObject * arg3 ), {
185- return wasmTable .get (func )(arg1 , arg2 , arg3 );
186- });
187-
188- typedef PyObject * (* zero_arg )(void );
189- typedef PyObject * (* one_arg )(PyObject * );
190- typedef PyObject * (* two_arg )(PyObject * , PyObject * );
191- typedef PyObject * (* three_arg )(PyObject * , PyObject * , PyObject * );
192-
19345PyObject *
19446_PyEM_TrampolineCall (PyCFunctionWithKeywords func ,
19547 PyObject * self ,
19648 PyObject * args ,
19749 PyObject * kw )
19850{
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 ;
51+ int success = 1 ;
52+ PyObject * result = _PyEM_TrampolineCall_inner (& success , func , self , args , kw );
53+ if (!success ) {
54+ PyErr_SetString (PyExc_SystemError , "Handler takes too many arguments" );
21555 }
56+ return result ;
21657}
21758
21859#endif
0 commit comments