From b883d2800d9307064d278118df9a1ea9bab91941 Mon Sep 17 00:00:00 2001 From: Hood Chatham Date: Tue, 22 Oct 2024 15:16:03 +0200 Subject: [PATCH 01/12] gh-128627: Emscripten: Use wasm-gc based call adaptor if available Part of the ongoing quest to support JSPI. The JSPI spec removed its dependence on JS type reflection, and now the plan is for runtimes to ship JSPI while keeping type reflection in stage 3. So we need an alternative way to count the number of parameters of a function. It is possible to count them by repeatedly trying to instantiate a webassembly module with the function as an import of a different type signature. But this is pretty inefficient. Since WebAssembly gc is now stage 4, there is a new option. WebAssembly gc added the `ref.test` instruction which can ask if a funcref has a given type. It's a bit difficult to apply because even our usual assembler the wasm binary toolkit doesn't support this instruction yet. But all JS engines that support JSPI support it. We just have to do some manual work to produce the binary. This code also has to be written carefully to interact properly with memory snapshots. Importantly, no JS initialization code can be called from the C initialization code. For this reason, we make a C function pointer to fill from JS and fill it in a preRun function. --- .../internal/pycore_emscripten_trampoline.h | 24 +- Python/emscripten_trampoline.c | 252 +++++++++++++----- Python/pylifecycle.c | 1 + Python/pystate.c | 5 - 4 files changed, 191 insertions(+), 91 deletions(-) diff --git a/Include/internal/pycore_emscripten_trampoline.h b/Include/internal/pycore_emscripten_trampoline.h index e519c99ad86cce..e1672359541b98 100644 --- a/Include/internal/pycore_emscripten_trampoline.h +++ b/Include/internal/pycore_emscripten_trampoline.h @@ -26,25 +26,11 @@ */ #if defined(__EMSCRIPTEN__) && defined(PY_CALL_TRAMPOLINE) - -void _Py_EmscriptenTrampoline_Init(_PyRuntimeState *runtime); - -PyObject* -_PyEM_TrampolineCall_JavaScript(PyCFunctionWithKeywords func, - PyObject* self, - PyObject* args, - PyObject* kw); - PyObject* -_PyEM_TrampolineCall_Reflection(PyCFunctionWithKeywords func, - PyObject* self, - PyObject* args, - PyObject* kw); - -#define _PyEM_TrampolineCall(meth, self, args, kw) \ - ((_PyRuntime.wasm_type_reflection_available) ? \ - (_PyEM_TrampolineCall_Reflection((PyCFunctionWithKeywords)(meth), (self), (args), (kw))) : \ - (_PyEM_TrampolineCall_JavaScript((PyCFunctionWithKeywords)(meth), (self), (args), (kw)))) +_PyEM_TrampolineCall(PyCFunctionWithKeywords func, + PyObject* self, + PyObject* args, + PyObject* kw); #define _PyCFunction_TrampolineCall(meth, self, args) \ _PyEM_TrampolineCall( \ @@ -62,8 +48,6 @@ _PyEM_TrampolineCall_Reflection(PyCFunctionWithKeywords func, #else // defined(__EMSCRIPTEN__) && defined(PY_CALL_TRAMPOLINE) -#define _Py_EmscriptenTrampoline_Init(runtime) - #define _PyCFunction_TrampolineCall(meth, self, args) \ (meth)((self), (args)) diff --git a/Python/emscripten_trampoline.c b/Python/emscripten_trampoline.c index 960c6b4a2ef995..fa976f0430eddb 100644 --- a/Python/emscripten_trampoline.c +++ b/Python/emscripten_trampoline.c @@ -1,92 +1,212 @@ #if defined(PY_CALL_TRAMPOLINE) -#include // EM_JS +#include // EM_JS, EM_JS_DEPS #include #include "pycore_runtime.h" // _PyRuntime -/** - * This is the GoogleChromeLabs approved way to feature detect type-reflection: - * https://github.com/GoogleChromeLabs/wasm-feature-detect/blob/main/src/detectors/type-reflection/index.js - */ -EM_JS(int, _PyEM_detect_type_reflection, (), { - if (!("Function" in WebAssembly)) { - return false; - } - if (WebAssembly.Function.type) { - // Node v20 - Module.PyEM_CountArgs = (func) => WebAssembly.Function.type(wasmTable.get(func)).parameters.length; - } else { - // Node >= 22, v8-based browsers - Module.PyEM_CountArgs = (func) => wasmTable.get(func).type().parameters.length; - } - return true; -}); - -void -_Py_EmscriptenTrampoline_Init(_PyRuntimeState *runtime) -{ - runtime->wasm_type_reflection_available = _PyEM_detect_type_reflection(); -} +typedef int (*CountArgsFunc)(PyCFunctionWithKeywords func); +// 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. +EMSCRIPTEN_KEEPALIVE CountArgsFunc _PyEM_CountFuncParams = NULL; /** * Backwards compatible trampoline works with all JS runtimes */ -EM_JS(PyObject*, -_PyEM_TrampolineCall_JavaScript, (PyCFunctionWithKeywords func, - PyObject *arg1, - PyObject *arg2, - PyObject *arg3), -{ +EM_JS(PyObject*, _PyEM_TrampolineCall_JS, (PyCFunctionWithKeywords func, PyObject *arg1, PyObject *arg2, PyObject *arg3), { return wasmTable.get(func)(arg1, arg2, arg3); } -); -/** - * In runtimes with WebAssembly type reflection, count the number of parameters - * and cast to the appropriate signature - */ -EM_JS(int, _PyEM_CountFuncParams, (PyCFunctionWithKeywords func), -{ - let n = _PyEM_CountFuncParams.cache.get(func); +// 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 module to the binary, my approach is to find and +// replace "ref.test $type" -> "drop i32.const n" on the source text. This +// results in the bytes "0x1a, 0x41, n" where we need the bytes "0xfb, 0x14, n" +// so doing a find and replace on the output from "0x1a, 0x41" -> "0xfb, 0x14" +// gets us the output we need. +// +// (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 $type4 (func (param i32 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 $type4 +// (block $b (type $blocktype) +// i32.eqz +// br_if $b +// i32.const 4 +// return +// ) +// local.get $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 +// ) +// ref.test $type1 +// i32.const 1 +// (block $b (type $blocktype) +// i32.eqz +// br_if $b +// i32.const 1 +// return +// ) +// ref.test $type0 +// i32.const 0 +// (block $b (type $blocktype) +// i32.eqz +// br_if $b +// i32.const 0 +// return +// ) +// i32.const -1 +// ) +// ) +addOnPreRun(() => { + // Try to initialize countArgsFunc + const code = new Uint8Array([ + 0x00, 0x61, 0x73, 0x6d, // \0asm magic number + 0x01, 0x00, 0x00, 0x00, // version 1 + 0x01, 0x23, // Type section, body is 0x23 bytes + 0x06, // 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, 0x04, 0x7f, 0x7f, 0x7f, 0x7f, 0x01, 0x7f, // (type $type4 (func (param i32 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 - if (n !== undefined) { - return n; - } - n = Module.PyEM_CountArgs(func); - _PyEM_CountFuncParams.cache.set(func, n); - return n; -} -_PyEM_CountFuncParams.cache = new Map(); -) + 0x0a, 0x52, // Code section, + 0x01, 0x50, // 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, 0x04, // ref.test $type4 + 0x02, 0x05, // block $b (type $blocktype) + 0x45, // i32.eqz + 0x0d, 0x00, // br_if $b + 0x41, 0x04, // i32.const 4 + 0x0f, // return + 0x0b, // end block + + 0x20, 0x01, // local.get $fref + 0xfb, 0x14, 0x03, // ref.test $type3 + 0x02, 0x05, // 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, 0x05, // 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, 0x05, // 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, 0x05, // 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 + ]); + let ptr = 0; + try { + const mod = new WebAssembly.Module(code); + const inst = new WebAssembly.Instance(mod, {e: {t: wasmTable}}); + ptr = addFunction(inst.exports.f); + } catch(e) { + // If something goes wrong, we'll null out _PyEM_CountFuncParams and fall + // back to the JS trampoline. + } + HEAP32[__PyEM_CountFuncParams/4] = ptr; +}); +); 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_Reflection(PyCFunctionWithKeywords func, - PyObject* self, - PyObject* args, - PyObject* kw) +_PyEM_TrampolineCall(PyCFunctionWithKeywords func, + PyObject* self, + PyObject* args, + PyObject* kw) { - switch (_PyEM_CountFuncParams(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; - } + if (_PyEM_CountFuncParams == 0) { + return _PyEM_TrampolineCall_JS(func, self, args, kw); + } + switch (_PyEM_CountFuncParams(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 diff --git a/Python/pylifecycle.c b/Python/pylifecycle.c index 06418123d6dd9b..b984f97bcb60d8 100644 --- a/Python/pylifecycle.c +++ b/Python/pylifecycle.c @@ -7,6 +7,7 @@ #include "pycore_ceval.h" // _PyEval_FiniGIL() #include "pycore_codecs.h" // _PyCodec_Lookup() #include "pycore_context.h" // _PyContext_Init() +#include "pycore_exceptions.h" // _PyExc_InitTypes() #include "pycore_dict.h" // _PyDict_Fini() #include "pycore_exceptions.h" // _PyExc_InitTypes() #include "pycore_fileutils.h" // _Py_ResetForceASCII() diff --git a/Python/pystate.c b/Python/pystate.c index c546b7c3a9f10e..814fdd6341c6e6 100644 --- a/Python/pystate.c +++ b/Python/pystate.c @@ -8,7 +8,6 @@ #include "pycore_code.h" // stats #include "pycore_critical_section.h" // _PyCriticalSection_Resume() #include "pycore_dtoa.h" // _dtoa_state_INIT() -#include "pycore_emscripten_trampoline.h" // _Py_EmscriptenTrampoline_Init() #include "pycore_frame.h" #include "pycore_freelist.h" // _PyObject_ClearFreeLists() #include "pycore_initconfig.h" // _PyStatus_OK() @@ -432,10 +431,6 @@ init_runtime(_PyRuntimeState *runtime, runtime->unicode_state.ids.next_index = unicode_next_index; -#if defined(__EMSCRIPTEN__) && defined(PY_CALL_TRAMPOLINE) - _Py_EmscriptenTrampoline_Init(runtime); -#endif - runtime->_initialized = 1; } From 0810bf137151b41acb5c999a83a9b6f8257c5762 Mon Sep 17 00:00:00 2001 From: Hood Chatham Date: Wed, 8 Jan 2025 13:23:42 +0100 Subject: [PATCH 02/12] Move top level constant into _PyRuntime --- Include/internal/pycore_runtime.h | 5 +++++ Python/emscripten_trampoline.c | 22 +++++++++++++++------- 2 files changed, 20 insertions(+), 7 deletions(-) diff --git a/Include/internal/pycore_runtime.h b/Include/internal/pycore_runtime.h index 86d024535fdda8..57be9796d985bc 100644 --- a/Include/internal/pycore_runtime.h +++ b/Include/internal/pycore_runtime.h @@ -67,6 +67,11 @@ typedef struct pyruntimestate { /* Is Python fully initialized? Set to 1 by Py_Initialize() */ int initialized; +#if defined(__EMSCRIPTEN__) && defined(PY_CALL_TRAMPOLINE) + /* Choose between trampoline based on type reflection vs based on EM_JS */ + int (*emscripten_count_args_function)(PyCFunctionWithKeywords func); +#endif + /* Set by Py_FinalizeEx(). Only reset to NULL if Py_Initialize() is called again. diff --git a/Python/emscripten_trampoline.c b/Python/emscripten_trampoline.c index fa976f0430eddb..de69d405485a1f 100644 --- a/Python/emscripten_trampoline.c +++ b/Python/emscripten_trampoline.c @@ -4,19 +4,23 @@ #include #include "pycore_runtime.h" // _PyRuntime +#define EMSCRIPTEN_COUNT_ARGS_OFFSET 20 + +_Static_assert(offsetof(_PyRuntimeState, emscripten_count_args_function) == EMSCRIPTEN_COUNT_ARGS_OFFSET); + +// Enable macro expanding in the body of EM_JS +#define EM_JS_MACROS(ret, func_name, args, body...) \ + EM_JS(ret, func_name, args, body) -typedef int (*CountArgsFunc)(PyCFunctionWithKeywords func); // 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. -EMSCRIPTEN_KEEPALIVE CountArgsFunc _PyEM_CountFuncParams = NULL; - /** * Backwards compatible trampoline works with all JS runtimes */ -EM_JS(PyObject*, _PyEM_TrampolineCall_JS, (PyCFunctionWithKeywords func, PyObject *arg1, PyObject *arg2, PyObject *arg3), { +EM_JS_MACROS(PyObject*, _PyEM_TrampolineCall_JS, (PyCFunctionWithKeywords func, PyObject *arg1, PyObject *arg2, PyObject *arg3), { return wasmTable.get(func)(arg1, arg2, arg3); } @@ -176,7 +180,7 @@ addOnPreRun(() => { // If something goes wrong, we'll null out _PyEM_CountFuncParams and fall // back to the JS trampoline. } - HEAP32[__PyEM_CountFuncParams/4] = ptr; + HEAP32[__PyRuntime/4 + EMSCRIPTEN_COUNT_ARGS_OFFSET] = ptr; }); ); @@ -185,16 +189,20 @@ typedef PyObject* (*one_arg)(PyObject*); typedef PyObject* (*two_arg)(PyObject*, PyObject*); typedef PyObject* (*three_arg)(PyObject*, PyObject*, PyObject*); +typedef int (*CountArgsFunc)(PyCFunctionWithKeywords func); + + PyObject* _PyEM_TrampolineCall(PyCFunctionWithKeywords func, PyObject* self, PyObject* args, PyObject* kw) { - if (_PyEM_CountFuncParams == 0) { + CountArgsFunc count_args = _PyRuntime.emscripten_count_args_function; + if (count_args == 0) { return _PyEM_TrampolineCall_JS(func, self, args, kw); } - switch (_PyEM_CountFuncParams(func)) { + switch (count_args(func)) { case 0: return ((zero_arg)func)(); case 1: From 8939a8b63a57b3e917248d5328a62aa3c458020d Mon Sep 17 00:00:00 2001 From: Hood Chatham Date: Wed, 8 Jan 2025 13:30:57 +0100 Subject: [PATCH 03/12] Revert accidental change --- Python/pylifecycle.c | 1 - 1 file changed, 1 deletion(-) diff --git a/Python/pylifecycle.c b/Python/pylifecycle.c index b984f97bcb60d8..06418123d6dd9b 100644 --- a/Python/pylifecycle.c +++ b/Python/pylifecycle.c @@ -7,7 +7,6 @@ #include "pycore_ceval.h" // _PyEval_FiniGIL() #include "pycore_codecs.h" // _PyCodec_Lookup() #include "pycore_context.h" // _PyContext_Init() -#include "pycore_exceptions.h" // _PyExc_InitTypes() #include "pycore_dict.h" // _PyDict_Fini() #include "pycore_exceptions.h" // _PyExc_InitTypes() #include "pycore_fileutils.h" // _Py_ResetForceASCII() From aa37fd54d4853860a563c77a064dd96cd99352f5 Mon Sep 17 00:00:00 2001 From: Hood Chatham Date: Wed, 8 Jan 2025 13:31:59 +0100 Subject: [PATCH 04/12] Fix indentation --- Python/emscripten_trampoline.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Python/emscripten_trampoline.c b/Python/emscripten_trampoline.c index de69d405485a1f..f61e047839eb54 100644 --- a/Python/emscripten_trampoline.c +++ b/Python/emscripten_trampoline.c @@ -194,9 +194,9 @@ typedef int (*CountArgsFunc)(PyCFunctionWithKeywords func); PyObject* _PyEM_TrampolineCall(PyCFunctionWithKeywords func, - PyObject* self, - PyObject* args, - PyObject* kw) + PyObject* self, + PyObject* args, + PyObject* kw) { CountArgsFunc count_args = _PyRuntime.emscripten_count_args_function; if (count_args == 0) { From 0343fe36638fdf0f9b1cbe02503a58b5e80e8496 Mon Sep 17 00:00:00 2001 From: Hood Chatham Date: Wed, 8 Jan 2025 22:13:41 +0100 Subject: [PATCH 05/12] Fix initialization --- .../internal/pycore_emscripten_trampoline.h | 4 ++ Python/emscripten_trampoline.c | 37 ++++++++++++------- Python/pystate.c | 5 +++ 3 files changed, 32 insertions(+), 14 deletions(-) diff --git a/Include/internal/pycore_emscripten_trampoline.h b/Include/internal/pycore_emscripten_trampoline.h index e1672359541b98..5546ebbbfcb5c1 100644 --- a/Include/internal/pycore_emscripten_trampoline.h +++ b/Include/internal/pycore_emscripten_trampoline.h @@ -26,6 +26,10 @@ */ #if defined(__EMSCRIPTEN__) && defined(PY_CALL_TRAMPOLINE) + +void +_Py_EmscriptenTrampoline_Init(_PyRuntimeState *runtime); + PyObject* _PyEM_TrampolineCall(PyCFunctionWithKeywords func, PyObject* self, diff --git a/Python/emscripten_trampoline.c b/Python/emscripten_trampoline.c index f61e047839eb54..c5de90c4bdc828 100644 --- a/Python/emscripten_trampoline.c +++ b/Python/emscripten_trampoline.c @@ -8,22 +8,15 @@ _Static_assert(offsetof(_PyRuntimeState, emscripten_count_args_function) == EMSCRIPTEN_COUNT_ARGS_OFFSET); +typedef int (*CountArgsFunc)(PyCFunctionWithKeywords func); + // Enable macro expanding in the body of EM_JS #define EM_JS_MACROS(ret, func_name, args, body...) \ EM_JS(ret, func_name, args, body) -// 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_MACROS(PyObject*, _PyEM_TrampolineCall_JS, (PyCFunctionWithKeywords func, PyObject *arg1, PyObject *arg2, PyObject *arg3), { - return wasmTable.get(func)(arg1, arg2, arg3); +EM_JS_MACROS(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 @@ -180,18 +173,34 @@ addOnPreRun(() => { // If something goes wrong, we'll null out _PyEM_CountFuncParams and fall // back to the JS trampoline. } + Module._PyEM_CountArgsPtr = ptr; HEAP32[__PyRuntime/4 + EMSCRIPTEN_COUNT_ARGS_OFFSET] = 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*); -typedef int (*CountArgsFunc)(PyCFunctionWithKeywords func); - - PyObject* _PyEM_TrampolineCall(PyCFunctionWithKeywords func, PyObject* self, diff --git a/Python/pystate.c b/Python/pystate.c index 814fdd6341c6e6..c546b7c3a9f10e 100644 --- a/Python/pystate.c +++ b/Python/pystate.c @@ -8,6 +8,7 @@ #include "pycore_code.h" // stats #include "pycore_critical_section.h" // _PyCriticalSection_Resume() #include "pycore_dtoa.h" // _dtoa_state_INIT() +#include "pycore_emscripten_trampoline.h" // _Py_EmscriptenTrampoline_Init() #include "pycore_frame.h" #include "pycore_freelist.h" // _PyObject_ClearFreeLists() #include "pycore_initconfig.h" // _PyStatus_OK() @@ -431,6 +432,10 @@ init_runtime(_PyRuntimeState *runtime, runtime->unicode_state.ids.next_index = unicode_next_index; +#if defined(__EMSCRIPTEN__) && defined(PY_CALL_TRAMPOLINE) + _Py_EmscriptenTrampoline_Init(runtime); +#endif + runtime->_initialized = 1; } From 88d2c8e08596c9702299024bcee5b25953ce9b56 Mon Sep 17 00:00:00 2001 From: Hood Chatham Date: Thu, 9 Jan 2025 14:29:42 +0100 Subject: [PATCH 06/12] two space indent --- Python/emscripten_trampoline.c | 110 ++++++++++++++++----------------- 1 file changed, 55 insertions(+), 55 deletions(-) diff --git a/Python/emscripten_trampoline.c b/Python/emscripten_trampoline.c index c5de90c4bdc828..a4a7f16eaf16fa 100644 --- a/Python/emscripten_trampoline.c +++ b/Python/emscripten_trampoline.c @@ -27,60 +27,60 @@ EM_JS_MACROS(CountArgsFunc, _PyEM_GetCountArgsPtr, (), { // gets us the output we need. // // (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 $type4 (func (param i32 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 $type4 -// (block $b (type $blocktype) -// i32.eqz -// br_if $b -// i32.const 4 -// return -// ) -// local.get $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 -// ) -// ref.test $type1 -// i32.const 1 -// (block $b (type $blocktype) -// i32.eqz -// br_if $b -// i32.const 1 -// return -// ) -// ref.test $type0 -// i32.const 0 -// (block $b (type $blocktype) -// i32.eqz -// br_if $b -// i32.const 0 -// return -// ) -// i32.const -1 +// (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 $type4 (func (param i32 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 $type4 +// (block $b (type $blocktype) +// i32.eqz +// br_if $b +// i32.const 4 +// return // ) +// local.get $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 +// ) +// ref.test $type1 +// i32.const 1 +// (block $b (type $blocktype) +// i32.eqz +// br_if $b +// i32.const 1 +// return +// ) +// ref.test $type0 +// i32.const 0 +// (block $b (type $blocktype) +// i32.eqz +// br_if $b +// i32.const 0 +// return +// ) +// i32.const -1 +// ) // ) addOnPreRun(() => { // Try to initialize countArgsFunc @@ -181,7 +181,7 @@ addOnPreRun(() => { void _Py_EmscriptenTrampoline_Init(_PyRuntimeState *runtime) { - runtime->emscripten_count_args_function = _PyEM_GetCountArgsPtr(); + runtime->emscripten_count_args_function = _PyEM_GetCountArgsPtr(); } // We have to be careful to work correctly with memory snapshots. Even if we are @@ -193,7 +193,7 @@ _Py_EmscriptenTrampoline_Init(_PyRuntimeState *runtime) * 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); + return wasmTable.get(func)(arg1, arg2, arg3); }); typedef PyObject* (*zero_arg)(void); From b8a2c06c5694a1cb2efcfc3fe2571c93f1ed5376 Mon Sep 17 00:00:00 2001 From: Hood Chatham Date: Thu, 9 Jan 2025 14:30:26 +0100 Subject: [PATCH 07/12] Fix pycore_runtime merge --- Include/internal/pycore_runtime.h | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/Include/internal/pycore_runtime.h b/Include/internal/pycore_runtime.h index 57be9796d985bc..cf123791eba9ac 100644 --- a/Include/internal/pycore_runtime.h +++ b/Include/internal/pycore_runtime.h @@ -67,11 +67,6 @@ typedef struct pyruntimestate { /* Is Python fully initialized? Set to 1 by Py_Initialize() */ int initialized; -#if defined(__EMSCRIPTEN__) && defined(PY_CALL_TRAMPOLINE) - /* Choose between trampoline based on type reflection vs based on EM_JS */ - int (*emscripten_count_args_function)(PyCFunctionWithKeywords func); -#endif - /* Set by Py_FinalizeEx(). Only reset to NULL if Py_Initialize() is called again. @@ -177,7 +172,7 @@ typedef struct pyruntimestate { #if defined(__EMSCRIPTEN__) && defined(PY_CALL_TRAMPOLINE) // Used in "Python/emscripten_trampoline.c" to choose between type // reflection trampoline and EM_JS trampoline. - bool wasm_type_reflection_available; + int (*emscripten_count_args_function)(PyCFunctionWithKeywords func); #endif /* All the objects that are shared by the runtime's interpreters. */ From aaed70dfb10522de8cc6b17c63eca99ff3a09ba8 Mon Sep 17 00:00:00 2001 From: Hood Chatham Date: Thu, 9 Jan 2025 14:48:17 +0100 Subject: [PATCH 08/12] Fix wat --- Python/emscripten_trampoline.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Python/emscripten_trampoline.c b/Python/emscripten_trampoline.c index a4a7f16eaf16fa..0d2902e009e861 100644 --- a/Python/emscripten_trampoline.c +++ b/Python/emscripten_trampoline.c @@ -63,16 +63,16 @@ EM_JS_MACROS(CountArgsFunc, _PyEM_GetCountArgsPtr, (), { // i32.const 2 // return // ) +// local.get $fref // ref.test $type1 -// i32.const 1 // (block $b (type $blocktype) // i32.eqz // br_if $b // i32.const 1 // return // ) +// local.get $fref // ref.test $type0 -// i32.const 0 // (block $b (type $blocktype) // i32.eqz // br_if $b From 6610fb649402493c2b4fac71b75eefa302854bac Mon Sep 17 00:00:00 2001 From: Hood Chatham Date: Thu, 9 Jan 2025 19:44:06 +0100 Subject: [PATCH 09/12] Add news entry --- .../next/Build/2025-01-09-19-44-00.gh-issue-128627.mHzsEd.rst | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 Misc/NEWS.d/next/Build/2025-01-09-19-44-00.gh-issue-128627.mHzsEd.rst diff --git a/Misc/NEWS.d/next/Build/2025-01-09-19-44-00.gh-issue-128627.mHzsEd.rst b/Misc/NEWS.d/next/Build/2025-01-09-19-44-00.gh-issue-128627.mHzsEd.rst new file mode 100644 index 00000000000000..a8c80ab6804b02 --- /dev/null +++ b/Misc/NEWS.d/next/Build/2025-01-09-19-44-00.gh-issue-128627.mHzsEd.rst @@ -0,0 +1,3 @@ +For Emscripten builds the function pointer cast call trampoline now uses the +wasm-gc ref.test instruction if it's available instead of Wasm JS type +reflection. From eda1648bab9fbd35f7c44c82e09b550e0787d921 Mon Sep 17 00:00:00 2001 From: Hood Chatham Date: Fri, 10 Jan 2025 16:08:58 +0100 Subject: [PATCH 10/12] Remove hard coded constant --- Python/emscripten_trampoline.c | 25 ++++++++++++++++--------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/Python/emscripten_trampoline.c b/Python/emscripten_trampoline.c index 0d2902e009e861..f51cd53896faf9 100644 --- a/Python/emscripten_trampoline.c +++ b/Python/emscripten_trampoline.c @@ -4,17 +4,23 @@ #include #include "pycore_runtime.h" // _PyRuntime -#define EMSCRIPTEN_COUNT_ARGS_OFFSET 20 - -_Static_assert(offsetof(_PyRuntimeState, emscripten_count_args_function) == EMSCRIPTEN_COUNT_ARGS_OFFSET); - typedef int (*CountArgsFunc)(PyCFunctionWithKeywords func); -// Enable macro expanding in the body of EM_JS -#define EM_JS_MACROS(ret, func_name, args, body...) \ - EM_JS(ret, func_name, args, body) +// 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_MACROS(CountArgsFunc, _PyEM_GetCountArgsPtr, (), { +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 @@ -174,7 +180,8 @@ addOnPreRun(() => { // back to the JS trampoline. } Module._PyEM_CountArgsPtr = ptr; - HEAP32[__PyRuntime/4 + EMSCRIPTEN_COUNT_ARGS_OFFSET] = ptr; + const offset = HEAP32[__PyEM_EMSCRIPTEN_COUNT_ARGS_OFFSET/4]; + HEAP32[__PyRuntime/4 + offset] = ptr; }); ); From b296fcc2d4aea461de5f9b00718a08b0ca231b83 Mon Sep 17 00:00:00 2001 From: Hood Chatham Date: Sat, 11 Jan 2025 23:24:15 +0100 Subject: [PATCH 11/12] Indent with 4 spaces --- Python/emscripten_trampoline.c | 320 ++++++++++++++++----------------- 1 file changed, 160 insertions(+), 160 deletions(-) diff --git a/Python/emscripten_trampoline.c b/Python/emscripten_trampoline.c index f51cd53896faf9..d7c74041e3187a 100644 --- a/Python/emscripten_trampoline.c +++ b/Python/emscripten_trampoline.c @@ -21,7 +21,7 @@ typedef int (*CountArgsFunc)(PyCFunctionWithKeywords func); 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 + 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 @@ -33,162 +33,162 @@ EM_JS(CountArgsFunc, _PyEM_GetCountArgsPtr, (), { // gets us the output we need. // // (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 $type4 (func (param i32 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 $type4 -// (block $b (type $blocktype) -// i32.eqz -// br_if $b -// i32.const 4 -// return +// (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 $type4 (func (param i32 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 $type4 +// (block $b (type $blocktype) +// i32.eqz +// br_if $b +// i32.const 4 +// return +// ) +// local.get $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 // ) -// local.get $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 -// ) // ) addOnPreRun(() => { - // Try to initialize countArgsFunc - const code = new Uint8Array([ - 0x00, 0x61, 0x73, 0x6d, // \0asm magic number - 0x01, 0x00, 0x00, 0x00, // version 1 - 0x01, 0x23, // Type section, body is 0x23 bytes - 0x06, // 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, 0x04, 0x7f, 0x7f, 0x7f, 0x7f, 0x01, 0x7f, // (type $type4 (func (param i32 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 + // Try to initialize countArgsFunc + const code = new Uint8Array([ + 0x00, 0x61, 0x73, 0x6d, // \0asm magic number + 0x01, 0x00, 0x00, 0x00, // version 1 + 0x01, 0x23, // Type section, body is 0x23 bytes + 0x06, // 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, 0x04, 0x7f, 0x7f, 0x7f, 0x7f, 0x01, 0x7f, // (type $type4 (func (param i32 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, 0x52, // Code section, - 0x01, 0x50, // 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, 0x04, // ref.test $type4 - 0x02, 0x05, // block $b (type $blocktype) - 0x45, // i32.eqz - 0x0d, 0x00, // br_if $b - 0x41, 0x04, // i32.const 4 - 0x0f, // return - 0x0b, // end block + 0x0a, 0x52, // Code section, + 0x01, 0x50, // 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, 0x04, // ref.test $type4 + 0x02, 0x05, // block $b (type $blocktype) + 0x45, // i32.eqz + 0x0d, 0x00, // br_if $b + 0x41, 0x04, // i32.const 4 + 0x0f, // return + 0x0b, // end block - 0x20, 0x01, // local.get $fref - 0xfb, 0x14, 0x03, // ref.test $type3 - 0x02, 0x05, // 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, 0x03, // ref.test $type3 + 0x02, 0x05, // 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, 0x05, // 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, 0x02, // ref.test $type2 + 0x02, 0x05, // 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, 0x05, // 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, 0x01, // ref.test $type1 + 0x02, 0x05, // 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, 0x05, // block $b (type $blocktype) - 0x45, // i32.eqz - 0x0d, 0x00, // br_if $b - 0x41, 0x00, // i32.const 0 - 0x0f, // return - 0x0b, // end block + 0x20, 0x01, // local.get $fref + 0xfb, 0x14, 0x00, // ref.test $type0 + 0x02, 0x05, // 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 - ]); - let ptr = 0; - try { - const mod = new WebAssembly.Module(code); - const inst = new WebAssembly.Instance(mod, {e: {t: wasmTable}}); - ptr = addFunction(inst.exports.f); - } catch(e) { - // If something goes wrong, we'll null out _PyEM_CountFuncParams and fall - // back to the JS trampoline. - } - Module._PyEM_CountArgsPtr = ptr; - const offset = HEAP32[__PyEM_EMSCRIPTEN_COUNT_ARGS_OFFSET/4]; - HEAP32[__PyRuntime/4 + offset] = ptr; + 0x41, 0x7f, // i32.const -1 + 0x0b // end function + ]); + let ptr = 0; + try { + const mod = new WebAssembly.Module(code); + const inst = new WebAssembly.Instance(mod, {e: {t: wasmTable}}); + ptr = addFunction(inst.exports.f); + } catch(e) { + // If something goes wrong, we'll null out _PyEM_CountFuncParams and fall + // back to the JS trampoline. + } + Module._PyEM_CountArgsPtr = ptr; + const offset = HEAP32[__PyEM_EMSCRIPTEN_COUNT_ARGS_OFFSET/4]; + HEAP32[__PyRuntime/4 + offset] = ptr; }); ); void _Py_EmscriptenTrampoline_Init(_PyRuntimeState *runtime) { - runtime->emscripten_count_args_function = _PyEM_GetCountArgsPtr(); + runtime->emscripten_count_args_function = _PyEM_GetCountArgsPtr(); } // We have to be careful to work correctly with memory snapshots. Even if we are @@ -200,7 +200,7 @@ _Py_EmscriptenTrampoline_Init(_PyRuntimeState *runtime) * 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); + return wasmTable.get(func)(arg1, arg2, arg3); }); typedef PyObject* (*zero_arg)(void); @@ -214,23 +214,23 @@ _PyEM_TrampolineCall(PyCFunctionWithKeywords func, 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; - } + 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 From b30d660ccbaf969646446e45c56c0f685311d1a3 Mon Sep 17 00:00:00 2001 From: Hood Chatham Date: Sun, 12 Jan 2025 21:19:07 +0100 Subject: [PATCH 12/12] Remove unneeded check for 4-arg type --- Python/emscripten_trampoline.c | 35 ++++++++-------------------------- 1 file changed, 8 insertions(+), 27 deletions(-) diff --git a/Python/emscripten_trampoline.c b/Python/emscripten_trampoline.c index d7c74041e3187a..2f9648a12ef2e4 100644 --- a/Python/emscripten_trampoline.c +++ b/Python/emscripten_trampoline.c @@ -37,7 +37,6 @@ EM_JS(CountArgsFunc, _PyEM_GetCountArgsPtr, (), { // (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 $type4 (func (param i32 i32 i32 i32) (result i32))) // (type $blocktype (func (param i32) (result))) // (table $funcs (import "e" "t") 0 funcref) // (export "f" (func $f)) @@ -46,14 +45,6 @@ EM_JS(CountArgsFunc, _PyEM_GetCountArgsPtr, (), { // local.get $fptr // table.get $funcs // local.tee $fref -// ref.test $type4 -// (block $b (type $blocktype) -// i32.eqz -// br_if $b -// i32.const 4 -// return -// ) -// local.get $fref // ref.test $type3 // (block $b (type $blocktype) // i32.eqz @@ -93,13 +84,12 @@ addOnPreRun(() => { const code = new Uint8Array([ 0x00, 0x61, 0x73, 0x6d, // \0asm magic number 0x01, 0x00, 0x00, 0x00, // version 1 - 0x01, 0x23, // Type section, body is 0x23 bytes - 0x06, // 6 entries + 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, 0x04, 0x7f, 0x7f, 0x7f, 0x7f, 0x01, 0x7f, // (type $type4 (func (param i32 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) @@ -116,24 +106,15 @@ addOnPreRun(() => { 0x00, // a function 0x00, // at index 0 - 0x0a, 0x52, // Code section, - 0x01, 0x50, // one entry of length 50 + 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, 0x04, // ref.test $type4 - 0x02, 0x05, // block $b (type $blocktype) - 0x45, // i32.eqz - 0x0d, 0x00, // br_if $b - 0x41, 0x04, // i32.const 4 - 0x0f, // return - 0x0b, // end block - - 0x20, 0x01, // local.get $fref 0xfb, 0x14, 0x03, // ref.test $type3 - 0x02, 0x05, // block $b (type $blocktype) + 0x02, 0x04, // block $b (type $blocktype) 0x45, // i32.eqz 0x0d, 0x00, // br_if $b 0x41, 0x03, // i32.const 3 @@ -142,7 +123,7 @@ addOnPreRun(() => { 0x20, 0x01, // local.get $fref 0xfb, 0x14, 0x02, // ref.test $type2 - 0x02, 0x05, // block $b (type $blocktype) + 0x02, 0x04, // block $b (type $blocktype) 0x45, // i32.eqz 0x0d, 0x00, // br_if $b 0x41, 0x02, // i32.const 2 @@ -151,7 +132,7 @@ addOnPreRun(() => { 0x20, 0x01, // local.get $fref 0xfb, 0x14, 0x01, // ref.test $type1 - 0x02, 0x05, // block $b (type $blocktype) + 0x02, 0x04, // block $b (type $blocktype) 0x45, // i32.eqz 0x0d, 0x00, // br_if $b 0x41, 0x01, // i32.const 1 @@ -160,7 +141,7 @@ addOnPreRun(() => { 0x20, 0x01, // local.get $fref 0xfb, 0x14, 0x00, // ref.test $type0 - 0x02, 0x05, // block $b (type $blocktype) + 0x02, 0x04, // block $b (type $blocktype) 0x45, // i32.eqz 0x0d, 0x00, // br_if $b 0x41, 0x00, // i32.const 0