11#if defined(PY_CALL_TRAMPOLINE )
22
3- #include <emscripten.h> // EM_JS
3+ #include <emscripten.h> // EM_JS, EM_JS_DEPS
44#include <Python.h>
55#include "pycore_runtime.h" // _PyRuntime
66
7+ typedef int (* CountArgsFunc )(PyCFunctionWithKeywords func );
78
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.
22162 }
23- return true;
163+ Module ._PyEM_CountArgsPtr = ptr ;
164+ const offset = HEAP32 [__PyEM_EMSCRIPTEN_COUNT_ARGS_OFFSET /4 ];
165+ HEAP32 [__PyRuntime /4 + offset ] = ptr ;
24166});
167+ );
25168
26169void
27170_Py_EmscriptenTrampoline_Init (_PyRuntimeState * runtime )
28171{
29- runtime -> wasm_type_reflection_available = _PyEM_detect_type_reflection ();
172+ runtime -> emscripten_count_args_function = _PyEM_GetCountArgsPtr ();
30173}
31174
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.
32180/**
33181 * Backwards compatible trampoline works with all JS runtimes
34182 */
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 ), {
41184 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+ });
63186
64187typedef PyObject * (* zero_arg )(void );
65188typedef PyObject * (* one_arg )(PyObject * );
66189typedef PyObject * (* two_arg )(PyObject * , PyObject * );
67190typedef PyObject * (* three_arg )(PyObject * , PyObject * , PyObject * );
68191
69-
70192PyObject *
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 )
75197{
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 )) {
77203 case 0 :
78204 return ((zero_arg )func )();
79205 case 1 :
@@ -83,8 +209,7 @@ _PyEM_TrampolineCall_Reflection(PyCFunctionWithKeywords func,
83209 case 3 :
84210 return ((three_arg )func )(self , args , kw );
85211 default :
86- PyErr_SetString (PyExc_SystemError ,
87- "Handler takes too many arguments" );
212+ PyErr_SetString (PyExc_SystemError , "Handler takes too many arguments" );
88213 return NULL ;
89214 }
90215}
0 commit comments