1
1
#if defined(PY_CALL_TRAMPOLINE )
2
2
3
- #include <emscripten.h> // EM_JS
3
+ #include <emscripten.h> // EM_JS, EM_JS_DEPS
4
4
#include <Python.h>
5
5
#include "pycore_runtime.h" // _PyRuntime
6
6
7
+ typedef int (* CountArgsFunc )(PyCFunctionWithKeywords func );
7
8
8
- /**
9
- * This is the GoogleChromeLabs approved way to feature detect type-reflection:
10
- * https://github.com/GoogleChromeLabs/wasm-feature-detect/blob/main/src/detectors/type-reflection/index.js
11
- */
12
- EM_JS (int , _PyEM_detect_type_reflection , ( ), {
13
- if (!("Function" in WebAssembly )) {
14
- return false;
15
- }
16
- if (WebAssembly .Function .type ) {
17
- // Node v20
18
- Module .PyEM_CountArgs = (func ) = > WebAssembly .Function .type (wasmTable .get (func )).parameters .length ;
19
- } else {
20
- // Node >= 22, v8-based browsers
21
- Module .PyEM_CountArgs = (func ) = > wasmTable .get (func ).type ().parameters .length ;
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
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 module to the binary, my approach is to find and
30
+ // replace "ref.test $type" -> "drop i32.const n" on the source text. This
31
+ // results in the bytes "0x1a, 0x41, n" where we need the bytes "0xfb, 0x14, n"
32
+ // so doing a find and replace on the output from "0x1a, 0x41" -> "0xfb, 0x14"
33
+ // gets us the output we need.
34
+ //
35
+ // (module
36
+ // (type $type0 (func (param) (result i32)))
37
+ // (type $type1 (func (param i32) (result i32)))
38
+ // (type $type2 (func (param i32 i32) (result i32)))
39
+ // (type $type3 (func (param i32 i32 i32) (result i32)))
40
+ // (type $blocktype (func (param i32) (result)))
41
+ // (table $funcs (import "e" "t") 0 funcref)
42
+ // (export "f" (func $f))
43
+ // (func $f (param $fptr i32) (result i32)
44
+ // (local $fref funcref)
45
+ // local.get $fptr
46
+ // table.get $funcs
47
+ // local.tee $fref
48
+ // ref.test $type3
49
+ // (block $b (type $blocktype)
50
+ // i32.eqz
51
+ // br_if $b
52
+ // i32.const 3
53
+ // return
54
+ // )
55
+ // local.get $fref
56
+ // ref.test $type2
57
+ // (block $b (type $blocktype)
58
+ // i32.eqz
59
+ // br_if $b
60
+ // i32.const 2
61
+ // return
62
+ // )
63
+ // local.get $fref
64
+ // ref.test $type1
65
+ // (block $b (type $blocktype)
66
+ // i32.eqz
67
+ // br_if $b
68
+ // i32.const 1
69
+ // return
70
+ // )
71
+ // local.get $fref
72
+ // ref.test $type0
73
+ // (block $b (type $blocktype)
74
+ // i32.eqz
75
+ // br_if $b
76
+ // i32.const 0
77
+ // return
78
+ // )
79
+ // i32.const -1
80
+ // )
81
+ // )
82
+ addOnPreRun (() = > {
83
+ // Try to initialize countArgsFunc
84
+ const code = new Uint8Array ([
85
+ 0x00 , 0x61 , 0x73 , 0x6d , // \0asm magic number
86
+ 0x01 , 0x00 , 0x00 , 0x00 , // version 1
87
+ 0x01 , 0x1b , // Type section, body is 0x1b bytes
88
+ 0x05 , // 6 entries
89
+ 0x60 , 0x00 , 0x01 , 0x7f , // (type $type0 (func (param) (result i32)))
90
+ 0x60 , 0x01 , 0x7f , 0x01 , 0x7f , // (type $type1 (func (param i32) (result i32)))
91
+ 0x60 , 0x02 , 0x7f , 0x7f , 0x01 , 0x7f , // (type $type2 (func (param i32 i32) (result i32)))
92
+ 0x60 , 0x03 , 0x7f , 0x7f , 0x7f , 0x01 , 0x7f , // (type $type3 (func (param i32 i32 i32) (result i32)))
93
+ 0x60 , 0x01 , 0x7f , 0x00 , // (type $blocktype (func (param i32) (result)))
94
+ 0x02 , 0x09 , // Import section, 0x9 byte body
95
+ 0x01 , // 1 import (table $funcs (import "e" "t") 0 funcref)
96
+ 0x01 , 0x65 , // "e"
97
+ 0x01 , 0x74 , // "t"
98
+ 0x01 , // importing a table
99
+ 0x70 , // of entry type funcref
100
+ 0x00 , 0x00 , // table limits: no max, min of 0
101
+ 0x03 , 0x02 , // Function section
102
+ 0x01 , 0x01 , // We're going to define one function of type 1 (func (param i32) (result i32))
103
+ 0x07 , 0x05 , // export section
104
+ 0x01 , // 1 export
105
+ 0x01 , 0x66 , // called "f"
106
+ 0x00 , // a function
107
+ 0x00 , // at index 0
108
+
109
+ 0x0a , 0x44 , // Code section,
110
+ 0x01 , 0x42 , // one entry of length 50
111
+ 0x01 , 0x01 , 0x70 , // one local of type funcref
112
+ // Body of the function
113
+ 0x20 , 0x00 , // local.get $fptr
114
+ 0x25 , 0x00 , // table.get $funcs
115
+ 0x22 , 0x01 , // local.tee $fref
116
+ 0xfb , 0x14 , 0x03 , // ref.test $type3
117
+ 0x02 , 0x04 , // block $b (type $blocktype)
118
+ 0x45 , // i32.eqz
119
+ 0x0d , 0x00 , // br_if $b
120
+ 0x41 , 0x03 , // i32.const 3
121
+ 0x0f , // return
122
+ 0x0b , // end block
123
+
124
+ 0x20 , 0x01 , // local.get $fref
125
+ 0xfb , 0x14 , 0x02 , // ref.test $type2
126
+ 0x02 , 0x04 , // block $b (type $blocktype)
127
+ 0x45 , // i32.eqz
128
+ 0x0d , 0x00 , // br_if $b
129
+ 0x41 , 0x02 , // i32.const 2
130
+ 0x0f , // return
131
+ 0x0b , // end block
132
+
133
+ 0x20 , 0x01 , // local.get $fref
134
+ 0xfb , 0x14 , 0x01 , // ref.test $type1
135
+ 0x02 , 0x04 , // block $b (type $blocktype)
136
+ 0x45 , // i32.eqz
137
+ 0x0d , 0x00 , // br_if $b
138
+ 0x41 , 0x01 , // i32.const 1
139
+ 0x0f , // return
140
+ 0x0b , // end block
141
+
142
+ 0x20 , 0x01 , // local.get $fref
143
+ 0xfb , 0x14 , 0x00 , // ref.test $type0
144
+ 0x02 , 0x04 , // block $b (type $blocktype)
145
+ 0x45 , // i32.eqz
146
+ 0x0d , 0x00 , // br_if $b
147
+ 0x41 , 0x00 , // i32.const 0
148
+ 0x0f , // return
149
+ 0x0b , // end block
150
+
151
+ 0x41 , 0x7f , // i32.const -1
152
+ 0x0b // end function
153
+ ]);
154
+ let ptr = 0 ;
155
+ try {
156
+ const mod = new WebAssembly .Module (code );
157
+ const inst = new WebAssembly .Instance (mod , {e : {t : wasmTable }});
158
+ ptr = addFunction (inst .exports .f );
159
+ } catch (e ) {
160
+ // If something goes wrong, we'll null out _PyEM_CountFuncParams and fall
161
+ // back to the JS trampoline.
22
162
}
23
- return true;
163
+ Module ._PyEM_CountArgsPtr = ptr ;
164
+ const offset = HEAP32 [__PyEM_EMSCRIPTEN_COUNT_ARGS_OFFSET /4 ];
165
+ HEAP32 [__PyRuntime /4 + offset ] = ptr ;
24
166
});
167
+ );
25
168
26
169
void
27
170
_Py_EmscriptenTrampoline_Init (_PyRuntimeState * runtime )
28
171
{
29
- runtime -> wasm_type_reflection_available = _PyEM_detect_type_reflection ();
172
+ runtime -> emscripten_count_args_function = _PyEM_GetCountArgsPtr ();
30
173
}
31
174
175
+ // We have to be careful to work correctly with memory snapshots. Even if we are
176
+ // loading a memory snapshot, we need to perform the JS initialization work.
177
+ // That means we can't call the initialization code from C. Instead, we export
178
+ // this function pointer to JS and then fill it in a preRun function which runs
179
+ // unconditionally.
32
180
/**
33
181
* Backwards compatible trampoline works with all JS runtimes
34
182
*/
35
- EM_JS (PyObject * ,
36
- _PyEM_TrampolineCall_JavaScript , (PyCFunctionWithKeywords func ,
37
- PyObject * arg1 ,
38
- PyObject * arg2 ,
39
- PyObject * arg3 ),
40
- {
183
+ EM_JS (PyObject * , _PyEM_TrampolineCall_JS , (PyCFunctionWithKeywords func , PyObject * arg1 , PyObject * arg2 , PyObject * arg3 ), {
41
184
return wasmTable .get (func )(arg1 , arg2 , arg3 );
42
- }
43
- );
44
-
45
- /**
46
- * In runtimes with WebAssembly type reflection, count the number of parameters
47
- * and cast to the appropriate signature
48
- */
49
- EM_JS (int , _PyEM_CountFuncParams , (PyCFunctionWithKeywords func ),
50
- {
51
- let n = _PyEM_CountFuncParams .cache .get (func );
52
-
53
- if (n != = undefined ) {
54
- return n ;
55
- }
56
- n = Module .PyEM_CountArgs (func );
57
- _PyEM_CountFuncParams .cache .set (func , n );
58
- return n ;
59
- }
60
- _PyEM_CountFuncParams .cache = new Map ();
61
- )
62
-
185
+ });
63
186
64
187
typedef PyObject * (* zero_arg )(void );
65
188
typedef PyObject * (* one_arg )(PyObject * );
66
189
typedef PyObject * (* two_arg )(PyObject * , PyObject * );
67
190
typedef PyObject * (* three_arg )(PyObject * , PyObject * , PyObject * );
68
191
69
-
70
192
PyObject *
71
- _PyEM_TrampolineCall_Reflection (PyCFunctionWithKeywords func ,
72
- PyObject * self ,
73
- PyObject * args ,
74
- PyObject * kw )
193
+ _PyEM_TrampolineCall (PyCFunctionWithKeywords func ,
194
+ PyObject * self ,
195
+ PyObject * args ,
196
+ PyObject * kw )
75
197
{
76
- switch (_PyEM_CountFuncParams (func )) {
198
+ CountArgsFunc count_args = _PyRuntime .emscripten_count_args_function ;
199
+ if (count_args == 0 ) {
200
+ return _PyEM_TrampolineCall_JS (func , self , args , kw );
201
+ }
202
+ switch (count_args (func )) {
77
203
case 0 :
78
204
return ((zero_arg )func )();
79
205
case 1 :
@@ -83,8 +209,7 @@ _PyEM_TrampolineCall_Reflection(PyCFunctionWithKeywords func,
83
209
case 3 :
84
210
return ((three_arg )func )(self , args , kw );
85
211
default :
86
- PyErr_SetString (PyExc_SystemError ,
87
- "Handler takes too many arguments" );
212
+ PyErr_SetString (PyExc_SystemError , "Handler takes too many arguments" );
88
213
return NULL ;
89
214
}
90
215
}
0 commit comments