mirror of
				https://github.com/python/cpython.git
				synced 2025-10-26 03:04:41 +00:00 
			
		
		
		
	 72ea3c030a
			
		
	
	
		72ea3c030a
		
			
		
	
	
	
	
		
			
			As of iOS 18.3.1, enabling wasm-gc breaks the interpreter. This disables the wasm-gc trampoline on iOS. Co-authored-by: Hood Chatham <roberthoodchatham@gmail.com>
		
			
				
	
	
		
			225 lines
		
	
	
	
		
			8.5 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			225 lines
		
	
	
	
		
			8.5 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
| #if defined(PY_CALL_TRAMPOLINE)
 | |
| 
 | |
| #include <emscripten.h>             // EM_JS, EM_JS_DEPS
 | |
| #include <Python.h>
 | |
| #include "pycore_runtime.h"         // _PyRuntime
 | |
| 
 | |
| typedef int (*CountArgsFunc)(PyCFunctionWithKeywords func);
 | |
| 
 | |
| // 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
 | |
| // 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.
 | |
| //
 | |
| // (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
 | |
| //     )
 | |
| // )
 | |
| 
 | |
| function getPyEMCountArgsPtr() {
 | |
|     let isIOS = globalThis.navigator && /iPad|iPhone|iPod/.test(navigator.platform);
 | |
|     if (isIOS) {
 | |
|         return 0;
 | |
|     }
 | |
| 
 | |
|     // 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);
 | |
|         const inst = new WebAssembly.Instance(mod, { e: { t: wasmTable } });
 | |
|         return addFunction(inst.exports.f);
 | |
|     } catch (e) {
 | |
|         // If something goes wrong, we'll null out _PyEM_CountFuncParams and fall
 | |
|         // back to the JS trampoline.
 | |
|         return 0;
 | |
|     }
 | |
| }
 | |
| 
 | |
| addOnPreRun(() => {
 | |
|     const ptr = getPyEMCountArgsPtr();
 | |
|     Module._PyEM_CountArgsPtr = ptr;
 | |
|     const offset = HEAP32[__PyEM_EMSCRIPTEN_COUNT_ARGS_OFFSET / 4];
 | |
|     HEAP32[(__PyRuntime + offset) / 4] = ptr;
 | |
| });
 | |
| );
 | |
| 
 | |
| void
 | |
| _Py_EmscriptenTrampoline_Init(_PyRuntimeState *runtime)
 | |
| {
 | |
|     runtime->emscripten_count_args_function = _PyEM_GetCountArgsPtr();
 | |
| }
 | |
| 
 | |
| // 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.
 | |
| /**
 | |
|  * Backwards compatible trampoline works with all JS runtimes
 | |
|  */
 | |
| EM_JS(PyObject*, _PyEM_TrampolineCall_JS, (PyCFunctionWithKeywords func, PyObject *arg1, PyObject *arg2, PyObject *arg3), {
 | |
|     return wasmTable.get(func)(arg1, arg2, arg3);
 | |
| });
 | |
| 
 | |
| typedef PyObject* (*zero_arg)(void);
 | |
| typedef PyObject* (*one_arg)(PyObject*);
 | |
| typedef PyObject* (*two_arg)(PyObject*, PyObject*);
 | |
| typedef PyObject* (*three_arg)(PyObject*, PyObject*, PyObject*);
 | |
| 
 | |
| PyObject*
 | |
| _PyEM_TrampolineCall(PyCFunctionWithKeywords func,
 | |
|                      PyObject* self,
 | |
|                      PyObject* args,
 | |
|                      PyObject* kw)
 | |
| {
 | |
|     CountArgsFunc count_args = _PyRuntime.emscripten_count_args_function;
 | |
|     if (count_args == 0) {
 | |
|         return _PyEM_TrampolineCall_JS(func, self, args, kw);
 | |
|     }
 | |
|     switch (count_args(func)) {
 | |
|         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:
 | |
|             PyErr_SetString(PyExc_SystemError, "Handler takes too many arguments");
 | |
|             return NULL;
 | |
|     }
 | |
| }
 | |
| 
 | |
| #endif
 |