--- /dev/null
+#ifndef Py_EMSCRIPTEN_TRAMPOLINE_H
+#define Py_EMSCRIPTEN_TRAMPOLINE_H
+
+#include "pycore_runtime.h" // _PyRuntimeState
+
+/**
+ * C function call trampolines to mitigate bad function pointer casts.
+ *
+ * Section 6.3.2.3, paragraph 8 reads:
+ *
+ * A pointer to a function of one type may be converted to a pointer to a
+ * function of another type and back again; the result shall compare equal to
+ * the original pointer. If a converted pointer is used to call a function
+ * whose type is not compatible with the pointed-to type, the behavior is
+ * undefined.
+ *
+ * Typical native ABIs ignore additional arguments or fill in missing values
+ * with 0/NULL in function pointer cast. Compilers do not show warnings when a
+ * function pointer is explicitly casted to an incompatible type.
+ *
+ * Bad fpcasts are an issue in WebAssembly. WASM's indirect_call has strict
+ * function signature checks. Argument count, types, and return type must match.
+ *
+ * Third party code unintentionally rely on problematic fpcasts. The call
+ * trampoline mitigates common occurrences of bad fpcasts on Emscripten.
+ */
+
+#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))))
+
+#define _PyCFunction_TrampolineCall(meth, self, args) \
+ _PyEM_TrampolineCall( \
+ (*(PyCFunctionWithKeywords)(void(*)(void))(meth)), (self), (args), NULL)
+
+#define _PyCFunctionWithKeywords_TrampolineCall(meth, self, args, kw) \
+ _PyEM_TrampolineCall((meth), (self), (args), (kw))
+
+#define descr_set_trampoline_call(set, obj, value, closure) \
+ ((int)_PyEM_TrampolineCall((PyCFunctionWithKeywords)(set), (obj), (value), (PyObject*)(closure)))
+
+#define descr_get_trampoline_call(get, obj, closure) \
+ _PyEM_TrampolineCall((PyCFunctionWithKeywords)(get), (obj), (PyObject*)(closure), NULL)
+
+
+#else // defined(__EMSCRIPTEN__) && defined(PY_CALL_TRAMPOLINE)
+
+#define _Py_EmscriptenTrampoline_Init(runtime)
+
+#define _PyCFunction_TrampolineCall(meth, self, args) \
+ (meth)((self), (args))
+
+#define _PyCFunctionWithKeywords_TrampolineCall(meth, self, args, kw) \
+ (meth)((self), (args), (kw))
+
+#define descr_set_trampoline_call(set, obj, value, closure) \
+ (set)((obj), (value), (closure))
+
+#define descr_get_trampoline_call(get, obj, closure) \
+ (get)((obj), (closure))
+
+#endif // defined(__EMSCRIPTEN__) && defined(PY_CALL_TRAMPOLINE)
+
+#endif // ndef Py_EMSCRIPTEN_SIGNAL_H
#include <stdbool.h>
#include "pycore_gc.h" // _PyObject_GC_IS_TRACKED()
+#include "pycore_emscripten_trampoline.h" // _PyCFunction_TrampolineCall()
#include "pycore_interp.h" // PyInterpreterState.gc
#include "pycore_pystate.h" // _PyInterpreterState_GET()
/* PyInterpreterState.interpreters.main */
PyInterpreterState _main_interpreter;
+
+#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;
+#endif
+
} _PyRuntimeState;
--- /dev/null
+Changed the way that Emscripten call trampolines work for compatibility with
+Wasm/JS Promise integration.
#include "pycore_abstract.h" // _PyObject_RealIsSubclass()
#include "pycore_call.h" // _PyStack_AsDict()
#include "pycore_ceval.h" // _Py_EnterRecursiveCallTstate()
+#include "pycore_emscripten_trampoline.h" // descr_set_trampoline_call(), descr_get_trampoline_call()
#include "pycore_descrobject.h" // _PyMethodWrapper_Type
#include "pycore_object.h" // _PyObject_GC_UNTRACK()
#include "pycore_pystate.h" // _PyThreadState_GET()
[clinic start generated code]*/
/*[clinic end generated code: output=da39a3ee5e6b4b0d input=556352653fd4c02e]*/
-// see pycore_object.h
-#if defined(__EMSCRIPTEN__) && defined(PY_CALL_TRAMPOLINE)
-#include <emscripten.h>
-EM_JS(int, descr_set_trampoline_call, (setter set, PyObject *obj, PyObject *value, void *closure), {
- return wasmTable.get(set)(obj, value, closure);
-});
-
-EM_JS(PyObject*, descr_get_trampoline_call, (getter get, PyObject *obj, void *closure), {
- return wasmTable.get(get)(obj, closure);
-});
-#else
-#define descr_set_trampoline_call(set, obj, value, closure) \
- (set)((obj), (value), (closure))
-
-#define descr_get_trampoline_call(get, obj, closure) \
- (get)((obj), (closure))
-
-#endif // __EMSCRIPTEN__ && PY_CALL_TRAMPOLINE
-
static void
descr_dealloc(PyDescrObject *descr)
{
return _Py_CheckFunctionResult(tstate, func, result, NULL);
}
-#if defined(__EMSCRIPTEN__) && defined(PY_CALL_TRAMPOLINE)
-#include <emscripten.h>
-
-EM_JS(PyObject*, _PyCFunctionWithKeywords_TrampolineCall, (PyCFunctionWithKeywords func, PyObject *self, PyObject *args, PyObject *kw), {
- return wasmTable.get(func)(self, args, kw);
-});
-#endif
--- /dev/null
+#if defined(PY_CALL_TRAMPOLINE)
+
+#include <emscripten.h> // EM_JS
+#include <Python.h>
+#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, (), {
+ return "Function" in WebAssembly;
+});
+
+void
+_Py_EmscriptenTrampoline_Init(_PyRuntimeState *runtime)
+{
+ runtime->wasm_type_reflection_available = _PyEM_detect_type_reflection();
+}
+
+/**
+ * Backwards compatible trampoline works with all JS runtimes
+ */
+EM_JS(PyObject*,
+_PyEM_TrampolineCall_JavaScript, (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);
+
+ if (n !== undefined) {
+ return n;
+ }
+ n = WebAssembly.Function.type(wasmTable.get(func)).parameters.length;
+ _PyEM_CountFuncParams.cache.set(func, n);
+ return n;
+}
+_PyEM_CountFuncParams.cache = new Map();
+)
+
+
+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)
+{
+ 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
#include "pycore_ceval.h"
#include "pycore_code.h" // stats
#include "pycore_dtoa.h" // _dtoa_state_INIT()
+#include "pycore_emscripten_trampoline.h" // _Py_EmscriptenTrampoline_Init()
#include "pycore_frame.h"
#include "pycore_initconfig.h" // _PyStatus_OK()
#include "pycore_object.h" // _PyType_InitCache()
runtime->unicode_state.ids.next_index = unicode_next_index;
+#if defined(__EMSCRIPTEN__) && defined(PY_CALL_TRAMPOLINE)
+ _Py_EmscriptenTrampoline_Init(runtime);
+#endif
+
runtime->_initialized = 1;
}
case $ac_sys_system in #(
Emscripten) :
- as_fn_append PLATFORM_OBJS ' Python/emscripten_signal.o'
- as_fn_append PLATFORM_HEADERS ' $(srcdir)/Include/internal/pycore_emscripten_signal.h'
+ as_fn_append PLATFORM_OBJS ' Python/emscripten_signal.o Python/emscripten_trampoline.o'
+ as_fn_append PLATFORM_HEADERS ' $(srcdir)/Include/internal/pycore_emscripten_signal.h $(srcdir)/Include/internal/pycore_emscripten_trampoline.h'
;; #(
*) :
;;
AS_CASE([$ac_sys_system],
[Emscripten], [
- AS_VAR_APPEND([PLATFORM_OBJS], [' Python/emscripten_signal.o'])
- AS_VAR_APPEND([PLATFORM_HEADERS], [' $(srcdir)/Include/internal/pycore_emscripten_signal.h'])
+ AS_VAR_APPEND([PLATFORM_OBJS], [' Python/emscripten_signal.o Python/emscripten_trampoline.o'])
+ AS_VAR_APPEND([PLATFORM_HEADERS], [' $(srcdir)/Include/internal/pycore_emscripten_signal.h $(srcdir)/Include/internal/pycore_emscripten_trampoline.h'])
],
)
AC_SUBST([PLATFORM_HEADERS])