mirror of
				https://github.com/python/cpython.git
				synced 2025-10-31 13:41:24 +00:00 
			
		
		
		
	gh-128627: Use __builtin_wasm_test_function_pointer_signature for Emscripten trampoline (#137470)
With https://github.com/llvm/llvm-project/pull/150201 being merged, there is now a better way to generate the Emscripten trampoline, instead of including hand-generated binary WASM content. Requires Emscripten 4.0.12.
This commit is contained in:
		
							parent
							
								
									81c975bcfc
								
							
						
					
					
						commit
						2629ee4eb0
					
				
					 9 changed files with 129 additions and 201 deletions
				
			
		|  | @ -2,75 +2,20 @@ | |||
| 
 | ||||
| #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
 | ||||
| EM_JS( | ||||
| PyObject*, | ||||
| _PyEM_TrampolineCall_inner, (int* success, | ||||
|                              PyCFunctionWithKeywords func, | ||||
|                              PyObject *arg1, | ||||
|                              PyObject *arg2, | ||||
|                              PyObject *arg3), { | ||||
|     // JavaScript fallback trampoline
 | ||||
|     return wasmTable.get(func)(arg1, arg2, arg3); | ||||
| } | ||||
| // 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) (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
 | ||||
| //         if $blocktype
 | ||||
| //             i32.const 3
 | ||||
| //             return
 | ||||
| //         end
 | ||||
| //         local.get $fref
 | ||||
| //         ref.test $type2
 | ||||
| //         if $blocktype
 | ||||
| //             i32.const 2
 | ||||
| //             return
 | ||||
| //         end
 | ||||
| //         local.get $fref
 | ||||
| //         ref.test $type1
 | ||||
| //         if $blocktype
 | ||||
| //             i32.const 1
 | ||||
| //             return
 | ||||
| //         end
 | ||||
| //         local.get $fref
 | ||||
| //         ref.test $type0
 | ||||
| //         if $blocktype
 | ||||
| //             i32.const 0
 | ||||
| //             return
 | ||||
| //         end
 | ||||
| //         i32.const -1
 | ||||
| //     )
 | ||||
| // )
 | ||||
| 
 | ||||
| function getPyEMCountArgsPtr() { | ||||
| // Try to replace the JS definition of _PyEM_TrampolineCall_inner with a wasm
 | ||||
| // version.
 | ||||
| (function () { | ||||
|     // Starting with iOS 18.3.1, WebKit on iOS has an issue with the garbage
 | ||||
|     // collector that breaks the call trampoline. See #130418 and
 | ||||
|     // https://bugs.webkit.org/show_bug.cgi?id=293113 for details.
 | ||||
|  | @ -82,137 +27,33 @@ function getPyEMCountArgsPtr() { | |||
|         (navigator.platform === 'MacIntel' && typeof navigator.maxTouchPoints !== 'undefined' && navigator.maxTouchPoints > 1) | ||||
|     ); | ||||
|     if (isIOS) { | ||||
|         return 0; | ||||
|         return; | ||||
|     } | ||||
| 
 | ||||
|     // Try to initialize countArgsFunc
 | ||||
|     const code = new Uint8Array([ | ||||
|         0x00, 0x61, 0x73, 0x6d, // \0asm magic number
 | ||||
|         0x01, 0x00, 0x00, 0x00, // version 1
 | ||||
|         0x01, 0x1a, // Type section, body is 0x1a 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, 0x00, 0x00,                            // (type $blocktype (func (param) (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, 56,  // Code section,
 | ||||
|             0x01, 54, // one entry of length 54
 | ||||
|             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
 | ||||
|             0x04, 0x04,       // if (type $blocktype)
 | ||||
|                 0x41, 0x03,   //   i32.const 3
 | ||||
|                 0x0f,         //   return
 | ||||
|             0x0b,             // end block
 | ||||
| 
 | ||||
|             0x20, 0x01,       // local.get $fref
 | ||||
|             0xfb, 0x14, 0x02, // ref.test $type2
 | ||||
|             0x04, 0x04,       // if (type $blocktype)
 | ||||
|                 0x41, 0x02,   //   i32.const 2
 | ||||
|                 0x0f,         //   return
 | ||||
|             0x0b,             // end block
 | ||||
| 
 | ||||
|             0x20, 0x01,       // local.get $fref
 | ||||
|             0xfb, 0x14, 0x01, // ref.test $type1
 | ||||
|             0x04, 0x04,       // if (type $blocktype)
 | ||||
|                 0x41, 0x01,   //   i32.const 1
 | ||||
|                 0x0f,         //   return
 | ||||
|             0x0b,             // end block
 | ||||
| 
 | ||||
|             0x20, 0x01,       // local.get $fref
 | ||||
|             0xfb, 0x14, 0x00, // ref.test $type0
 | ||||
|             0x04, 0x04,       // if (type $blocktype)
 | ||||
|                 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); | ||||
|         const trampolineModule = getWasmTrampolineModule(); | ||||
|         const trampolineInstance = new WebAssembly.Instance(trampolineModule, { | ||||
|             env: { __indirect_function_table: wasmTable, memory: wasmMemory }, | ||||
|         }); | ||||
|         _PyEM_TrampolineCall_inner = trampolineInstance.exports.trampoline_call; | ||||
|     } catch (e) { | ||||
|         // If something goes wrong, we'll null out _PyEM_CountFuncParams and fall
 | ||||
|         // back to the JS trampoline.
 | ||||
|         return 0; | ||||
|         // Compilation error due to missing wasm-gc support, fall back to JS
 | ||||
|         // trampoline
 | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| 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; | ||||
|     int success = 1; | ||||
|     PyObject *result = _PyEM_TrampolineCall_inner(&success, func, self, args, kw); | ||||
|     if (!success) { | ||||
|         PyErr_SetString(PyExc_SystemError, "Handler takes too many arguments"); | ||||
|     } | ||||
|     return result; | ||||
| } | ||||
| 
 | ||||
| #endif | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 Hood Chatham
						Hood Chatham