| 
									
										
										
										
											2023-09-15 15:04:21 -07:00
										 |  |  | #if defined(PY_CALL_TRAMPOLINE)
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-01-13 00:09:39 +01:00
										 |  |  | #include <emscripten.h>             // EM_JS, EM_JS_DEPS
 | 
					
						
							| 
									
										
										
										
											2023-09-15 15:04:21 -07:00
										 |  |  | #include <Python.h>
 | 
					
						
							|  |  |  | #include "pycore_runtime.h"         // _PyRuntime
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-01-13 00:09:39 +01:00
										 |  |  | typedef int (*CountArgsFunc)(PyCFunctionWithKeywords func); | 
					
						
							| 
									
										
										
										
											2023-09-15 15:04:21 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-01-13 00:09:39 +01:00
										 |  |  | // Offset of emscripten_count_args_function in _PyRuntimeState. There's a couple
 | 
					
						
							|  |  |  | // of alternatives:
 | 
					
						
							|  |  |  | // 1. Just make emscripten_count_args_function a real C global variable instead
 | 
					
						
							|  |  |  | //    of a field of _PyRuntimeState. This would violate our rule against mutable
 | 
					
						
							|  |  |  | //    globals.
 | 
					
						
							|  |  |  | // 2. #define a preprocessor constant equal to a hard coded number and make a
 | 
					
						
							|  |  |  | //    _Static_assert(offsetof(_PyRuntimeState, emscripten_count_args_function)
 | 
					
						
							|  |  |  | //    == OURCONSTANT) This has the disadvantage that we have to update the hard
 | 
					
						
							|  |  |  | //    coded constant when _PyRuntimeState changes
 | 
					
						
							|  |  |  | //
 | 
					
						
							|  |  |  | // So putting the mutable constant in _PyRuntime and using a immutable global to
 | 
					
						
							|  |  |  | // record the offset so we can access it from JS is probably the best way.
 | 
					
						
							|  |  |  | EMSCRIPTEN_KEEPALIVE const int _PyEM_EMSCRIPTEN_COUNT_ARGS_OFFSET = offsetof(_PyRuntimeState, emscripten_count_args_function); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | EM_JS(CountArgsFunc, _PyEM_GetCountArgsPtr, (), { | 
					
						
							|  |  |  |     return Module._PyEM_CountArgsPtr; // initialized below
 | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | // Binary module for the checks. It has to be done in web assembly because
 | 
					
						
							|  |  |  | // clang/llvm have no support yet for the reference types yet. In fact, the wasm
 | 
					
						
							|  |  |  | // binary toolkit doesn't yet support the ref.test instruction either. To
 | 
					
						
							| 
									
										
										
										
											2025-01-23 00:02:04 +00:00
										 |  |  | // convert the following textual wasm to a binary, you can build wabt from this
 | 
					
						
							|  |  |  | // branch: https://github.com/WebAssembly/wabt/pull/2529 and then use that
 | 
					
						
							|  |  |  | // wat2wasm binary.
 | 
					
						
							| 
									
										
										
										
											2025-01-13 00:09:39 +01:00
										 |  |  | //
 | 
					
						
							|  |  |  | // (module
 | 
					
						
							|  |  |  | //     (type $type0 (func (param) (result i32)))
 | 
					
						
							|  |  |  | //     (type $type1 (func (param i32) (result i32)))
 | 
					
						
							|  |  |  | //     (type $type2 (func (param i32 i32) (result i32)))
 | 
					
						
							|  |  |  | //     (type $type3 (func (param i32 i32 i32) (result i32)))
 | 
					
						
							|  |  |  | //     (type $blocktype (func (param i32) (result)))
 | 
					
						
							|  |  |  | //     (table $funcs (import "e" "t") 0 funcref)
 | 
					
						
							|  |  |  | //     (export "f" (func $f))
 | 
					
						
							|  |  |  | //     (func $f (param $fptr i32) (result i32)
 | 
					
						
							|  |  |  | //              (local $fref funcref)
 | 
					
						
							|  |  |  | //         local.get $fptr
 | 
					
						
							|  |  |  | //         table.get $funcs
 | 
					
						
							|  |  |  | //         local.tee $fref
 | 
					
						
							|  |  |  | //         ref.test $type3
 | 
					
						
							|  |  |  | //         (block $b (type $blocktype)
 | 
					
						
							|  |  |  | //             i32.eqz
 | 
					
						
							|  |  |  | //             br_if $b
 | 
					
						
							|  |  |  | //             i32.const 3
 | 
					
						
							|  |  |  | //             return
 | 
					
						
							|  |  |  | //         )
 | 
					
						
							|  |  |  | //         local.get $fref
 | 
					
						
							|  |  |  | //         ref.test $type2
 | 
					
						
							|  |  |  | //         (block $b (type $blocktype)
 | 
					
						
							|  |  |  | //             i32.eqz
 | 
					
						
							|  |  |  | //             br_if $b
 | 
					
						
							|  |  |  | //             i32.const 2
 | 
					
						
							|  |  |  | //             return
 | 
					
						
							|  |  |  | //         )
 | 
					
						
							|  |  |  | //         local.get $fref
 | 
					
						
							|  |  |  | //         ref.test $type1
 | 
					
						
							|  |  |  | //         (block $b (type $blocktype)
 | 
					
						
							|  |  |  | //             i32.eqz
 | 
					
						
							|  |  |  | //             br_if $b
 | 
					
						
							|  |  |  | //             i32.const 1
 | 
					
						
							|  |  |  | //             return
 | 
					
						
							|  |  |  | //         )
 | 
					
						
							|  |  |  | //         local.get $fref
 | 
					
						
							|  |  |  | //         ref.test $type0
 | 
					
						
							|  |  |  | //         (block $b (type $blocktype)
 | 
					
						
							|  |  |  | //             i32.eqz
 | 
					
						
							|  |  |  | //             br_if $b
 | 
					
						
							|  |  |  | //             i32.const 0
 | 
					
						
							|  |  |  | //             return
 | 
					
						
							|  |  |  | //         )
 | 
					
						
							|  |  |  | //         i32.const -1
 | 
					
						
							|  |  |  | //     )
 | 
					
						
							|  |  |  | // )
 | 
					
						
							| 
									
										
										
										
											2025-02-24 00:26:04 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  | function getPyEMCountArgsPtr() { | 
					
						
							|  |  |  |     let isIOS = globalThis.navigator && /iPad|iPhone|iPod/.test(navigator.platform); | 
					
						
							|  |  |  |     if (isIOS) { | 
					
						
							|  |  |  |         return 0; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-01-13 00:09:39 +01:00
										 |  |  |     // Try to initialize countArgsFunc
 | 
					
						
							|  |  |  |     const code = new Uint8Array([ | 
					
						
							|  |  |  |         0x00, 0x61, 0x73, 0x6d, // \0asm magic number
 | 
					
						
							|  |  |  |         0x01, 0x00, 0x00, 0x00, // version 1
 | 
					
						
							|  |  |  |         0x01, 0x1b, // Type section, body is 0x1b bytes
 | 
					
						
							|  |  |  |             0x05, // 6 entries
 | 
					
						
							|  |  |  |             0x60, 0x00, 0x01, 0x7f,                         // (type $type0 (func (param) (result i32)))
 | 
					
						
							|  |  |  |             0x60, 0x01, 0x7f, 0x01, 0x7f,                   // (type $type1 (func (param i32) (result i32)))
 | 
					
						
							|  |  |  |             0x60, 0x02, 0x7f, 0x7f, 0x01, 0x7f,             // (type $type2 (func (param i32 i32) (result i32)))
 | 
					
						
							|  |  |  |             0x60, 0x03, 0x7f, 0x7f, 0x7f, 0x01, 0x7f,       // (type $type3 (func (param i32 i32 i32) (result i32)))
 | 
					
						
							|  |  |  |             0x60, 0x01, 0x7f, 0x00,                         // (type $blocktype (func (param i32) (result)))
 | 
					
						
							|  |  |  |         0x02, 0x09, // Import section, 0x9 byte body
 | 
					
						
							|  |  |  |             0x01, // 1 import (table $funcs (import "e" "t") 0 funcref)
 | 
					
						
							|  |  |  |             0x01, 0x65, // "e"
 | 
					
						
							|  |  |  |             0x01, 0x74, // "t"
 | 
					
						
							|  |  |  |             0x01,       // importing a table
 | 
					
						
							|  |  |  |             0x70,       // of entry type funcref
 | 
					
						
							|  |  |  |             0x00, 0x00, // table limits: no max, min of 0
 | 
					
						
							|  |  |  |         0x03, 0x02,   // Function section
 | 
					
						
							|  |  |  |             0x01, 0x01, // We're going to define one function of type 1 (func (param i32) (result i32))
 | 
					
						
							|  |  |  |         0x07, 0x05, // export section
 | 
					
						
							|  |  |  |             0x01, // 1 export
 | 
					
						
							|  |  |  |             0x01, 0x66, // called "f"
 | 
					
						
							|  |  |  |             0x00, // a function
 | 
					
						
							|  |  |  |             0x00, // at index 0
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         0x0a, 0x44,  // Code section,
 | 
					
						
							|  |  |  |             0x01, 0x42, // one entry of length 50
 | 
					
						
							|  |  |  |             0x01, 0x01, 0x70, // one local of type funcref
 | 
					
						
							|  |  |  |             // Body of the function
 | 
					
						
							|  |  |  |             0x20, 0x00,       // local.get $fptr
 | 
					
						
							|  |  |  |             0x25, 0x00,       // table.get $funcs
 | 
					
						
							|  |  |  |             0x22, 0x01,       // local.tee $fref
 | 
					
						
							|  |  |  |             0xfb, 0x14, 0x03, // ref.test $type3
 | 
					
						
							|  |  |  |             0x02, 0x04,       // block $b (type $blocktype)
 | 
					
						
							|  |  |  |                 0x45,         //   i32.eqz
 | 
					
						
							|  |  |  |                 0x0d, 0x00,   //   br_if $b
 | 
					
						
							|  |  |  |                 0x41, 0x03,   //   i32.const 3
 | 
					
						
							|  |  |  |                 0x0f,         //   return
 | 
					
						
							|  |  |  |             0x0b,             // end block
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             0x20, 0x01,       // local.get $fref
 | 
					
						
							|  |  |  |             0xfb, 0x14, 0x02, // ref.test $type2
 | 
					
						
							|  |  |  |             0x02, 0x04,       // block $b (type $blocktype)
 | 
					
						
							|  |  |  |                 0x45,         //   i32.eqz
 | 
					
						
							|  |  |  |                 0x0d, 0x00,   //   br_if $b
 | 
					
						
							|  |  |  |                 0x41, 0x02,   //   i32.const 2
 | 
					
						
							|  |  |  |                 0x0f,         //   return
 | 
					
						
							|  |  |  |             0x0b,             // end block
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             0x20, 0x01,       // local.get $fref
 | 
					
						
							|  |  |  |             0xfb, 0x14, 0x01, // ref.test $type1
 | 
					
						
							|  |  |  |             0x02, 0x04,       // block $b (type $blocktype)
 | 
					
						
							|  |  |  |                 0x45,         //   i32.eqz
 | 
					
						
							|  |  |  |                 0x0d, 0x00,   //   br_if $b
 | 
					
						
							|  |  |  |                 0x41, 0x01,   //   i32.const 1
 | 
					
						
							|  |  |  |                 0x0f,         //   return
 | 
					
						
							|  |  |  |             0x0b,             // end block
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             0x20, 0x01,       // local.get $fref
 | 
					
						
							|  |  |  |             0xfb, 0x14, 0x00, // ref.test $type0
 | 
					
						
							|  |  |  |             0x02, 0x04,       // block $b (type $blocktype)
 | 
					
						
							|  |  |  |                 0x45,         //   i32.eqz
 | 
					
						
							|  |  |  |                 0x0d, 0x00,   //   br_if $b
 | 
					
						
							|  |  |  |                 0x41, 0x00,   //   i32.const 0
 | 
					
						
							|  |  |  |                 0x0f,         //   return
 | 
					
						
							|  |  |  |             0x0b,             // end block
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             0x41, 0x7f,       // i32.const -1
 | 
					
						
							|  |  |  |             0x0b // end function
 | 
					
						
							|  |  |  |     ]); | 
					
						
							|  |  |  |     try { | 
					
						
							|  |  |  |         const mod = new WebAssembly.Module(code); | 
					
						
							| 
									
										
										
										
											2025-01-23 00:02:04 +00:00
										 |  |  |         const inst = new WebAssembly.Instance(mod, { e: { t: wasmTable } }); | 
					
						
							| 
									
										
										
										
											2025-02-24 00:26:04 +01:00
										 |  |  |         return addFunction(inst.exports.f); | 
					
						
							| 
									
										
										
										
											2025-01-23 00:02:04 +00:00
										 |  |  |     } catch (e) { | 
					
						
							| 
									
										
										
										
											2025-01-13 00:09:39 +01:00
										 |  |  |         // If something goes wrong, we'll null out _PyEM_CountFuncParams and fall
 | 
					
						
							|  |  |  |         // back to the JS trampoline.
 | 
					
						
							| 
									
										
										
										
											2025-02-24 00:26:04 +01:00
										 |  |  |         return 0; | 
					
						
							| 
									
										
										
										
											2024-07-14 11:25:09 +02:00
										 |  |  |     } | 
					
						
							| 
									
										
										
										
											2025-02-24 00:26:04 +01:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | addOnPreRun(() => { | 
					
						
							|  |  |  |     const ptr = getPyEMCountArgsPtr(); | 
					
						
							| 
									
										
										
										
											2025-01-13 00:09:39 +01:00
										 |  |  |     Module._PyEM_CountArgsPtr = ptr; | 
					
						
							| 
									
										
										
										
											2025-01-23 00:02:04 +00:00
										 |  |  |     const offset = HEAP32[__PyEM_EMSCRIPTEN_COUNT_ARGS_OFFSET / 4]; | 
					
						
							|  |  |  |     HEAP32[(__PyRuntime + offset) / 4] = ptr; | 
					
						
							| 
									
										
										
										
											2023-09-15 15:04:21 -07:00
										 |  |  | }); | 
					
						
							| 
									
										
										
										
											2025-01-13 00:09:39 +01:00
										 |  |  | ); | 
					
						
							| 
									
										
										
										
											2023-09-15 15:04:21 -07:00
										 |  |  | 
 | 
					
						
							|  |  |  | void | 
					
						
							|  |  |  | _Py_EmscriptenTrampoline_Init(_PyRuntimeState *runtime) | 
					
						
							|  |  |  | { | 
					
						
							| 
									
										
										
										
											2025-01-13 00:09:39 +01:00
										 |  |  |     runtime->emscripten_count_args_function = _PyEM_GetCountArgsPtr(); | 
					
						
							| 
									
										
										
										
											2023-09-15 15:04:21 -07:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-01-13 00:09:39 +01:00
										 |  |  | // We have to be careful to work correctly with memory snapshots. Even if we are
 | 
					
						
							|  |  |  | // loading a memory snapshot, we need to perform the JS initialization work.
 | 
					
						
							|  |  |  | // That means we can't call the initialization code from C. Instead, we export
 | 
					
						
							|  |  |  | // this function pointer to JS and then fill it in a preRun function which runs
 | 
					
						
							|  |  |  | // unconditionally.
 | 
					
						
							| 
									
										
										
										
											2023-09-15 15:04:21 -07:00
										 |  |  | /**
 | 
					
						
							|  |  |  |  * Backwards compatible trampoline works with all JS runtimes | 
					
						
							|  |  |  |  */ | 
					
						
							| 
									
										
										
										
											2025-01-13 00:09:39 +01:00
										 |  |  | EM_JS(PyObject*, _PyEM_TrampolineCall_JS, (PyCFunctionWithKeywords func, PyObject *arg1, PyObject *arg2, PyObject *arg3), { | 
					
						
							| 
									
										
										
										
											2023-09-15 15:04:21 -07:00
										 |  |  |     return wasmTable.get(func)(arg1, arg2, arg3); | 
					
						
							| 
									
										
										
										
											2025-01-13 00:09:39 +01:00
										 |  |  | }); | 
					
						
							| 
									
										
										
										
											2023-09-15 15:04:21 -07:00
										 |  |  | 
 | 
					
						
							|  |  |  | typedef PyObject* (*zero_arg)(void); | 
					
						
							|  |  |  | typedef PyObject* (*one_arg)(PyObject*); | 
					
						
							|  |  |  | typedef PyObject* (*two_arg)(PyObject*, PyObject*); | 
					
						
							|  |  |  | typedef PyObject* (*three_arg)(PyObject*, PyObject*, PyObject*); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | PyObject* | 
					
						
							| 
									
										
										
										
											2025-01-13 00:09:39 +01:00
										 |  |  | _PyEM_TrampolineCall(PyCFunctionWithKeywords func, | 
					
						
							|  |  |  |                      PyObject* self, | 
					
						
							|  |  |  |                      PyObject* args, | 
					
						
							|  |  |  |                      PyObject* kw) | 
					
						
							| 
									
										
										
										
											2023-09-15 15:04:21 -07:00
										 |  |  | { | 
					
						
							| 
									
										
										
										
											2025-01-13 00:09:39 +01:00
										 |  |  |     CountArgsFunc count_args = _PyRuntime.emscripten_count_args_function; | 
					
						
							|  |  |  |     if (count_args == 0) { | 
					
						
							|  |  |  |         return _PyEM_TrampolineCall_JS(func, self, args, kw); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     switch (count_args(func)) { | 
					
						
							| 
									
										
										
										
											2023-09-15 15:04:21 -07:00
										 |  |  |         case 0: | 
					
						
							|  |  |  |             return ((zero_arg)func)(); | 
					
						
							|  |  |  |         case 1: | 
					
						
							|  |  |  |             return ((one_arg)func)(self); | 
					
						
							|  |  |  |         case 2: | 
					
						
							|  |  |  |             return ((two_arg)func)(self, args); | 
					
						
							|  |  |  |         case 3: | 
					
						
							|  |  |  |             return ((three_arg)func)(self, args, kw); | 
					
						
							|  |  |  |         default: | 
					
						
							| 
									
										
										
										
											2025-01-13 00:09:39 +01:00
										 |  |  |             PyErr_SetString(PyExc_SystemError, "Handler takes too many arguments"); | 
					
						
							| 
									
										
										
										
											2023-09-15 15:04:21 -07:00
										 |  |  |             return NULL; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | #endif
 |