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
17075void
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
0 commit comments