2
2
3
3
#include <emscripten.h> // EM_JS, EM_JS_DEPS
4
4
#include <Python.h>
5
- #include "pycore_runtime.h" // _PyRuntime
6
5
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 );
25
15
}
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 () {
74
19
// Starting with iOS 18.3.1, WebKit on iOS has an issue with the garbage
75
20
// collector that breaks the call trampoline. See #130418 and
76
21
// https://bugs.webkit.org/show_bug.cgi?id=293113 for details.
@@ -82,137 +27,33 @@ function getPyEMCountArgsPtr() {
82
27
(navigator .platform == = 'MacIntel' && typeof navigator .maxTouchPoints != = 'undefined' && navigator .maxTouchPoints > 1 )
83
28
);
84
29
if (isIOS ) {
85
- return 0 ;
30
+ return ;
86
31
}
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
32
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 ;
155
38
} 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
159
41
}
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
+ })();
168
43
);
169
44
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
-
193
45
PyObject *
194
46
_PyEM_TrampolineCall (PyCFunctionWithKeywords func ,
195
47
PyObject * self ,
196
48
PyObject * args ,
197
49
PyObject * kw )
198
50
{
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" );
215
55
}
56
+ return result ;
216
57
}
217
58
218
59
#endif
0 commit comments