Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

gh-106213: Make Emscripten trampolines work with JSPI #106219

Merged
merged 42 commits into from
Sep 15, 2023
Merged
Show file tree
Hide file tree
Changes from 25 commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
8e2ad18
Make Emscripten trampolines work with JSPI
hoodmane Jun 28, 2023
6903fd5
Add blurb
hoodmane Jun 29, 2023
af6850c
Add more comments
hoodmane Jun 29, 2023
ffb24ff
Fix typo
hoodmane Jun 29, 2023
85d88ec
Cleanup
hoodmane Jun 29, 2023
9d8f23e
Use EM_JS instead of EM_ASM
hoodmane Jun 30, 2023
2dc1399
Fix typo
hoodmane Jun 30, 2023
418efb4
Merge branch 'main' into jspi-trampoline
hoodmane Jul 5, 2023
d900dfd
Move type reflection flag into _PyRuntime
hoodmane Jul 6, 2023
62a2f7f
Merge branch 'main' into jspi-trampoline
hoodmane Jul 6, 2023
cd3f8bf
Update pycore_emscripten_trampoline.h declarations
hoodmane Jul 7, 2023
2c45f1b
Merge branch 'main' into jspi-trampoline
hoodmane Jul 7, 2023
3951848
Include pycore_runtime.h
hoodmane Jul 7, 2023
dfd43d7
Fix typo
hoodmane Jul 9, 2023
b0b50f4
Use wasmTable.get instead of getWasmTableEntry
hoodmane Jul 10, 2023
1c1b9ed
Replace another instance of getWasmTableEntry with wasmTable.get
hoodmane Jul 10, 2023
44f0371
Use branch in macro to choose trampoline
hoodmane Jul 10, 2023
bd609e5
Fix non emscripten platforms
hoodmane Jul 11, 2023
a00d94f
Fix compile warnings on non-emscripten platforms
hoodmane Jul 11, 2023
68f4dba
Fix spelling
hoodmane Jul 11, 2023
1d49e79
Fix formatting
hoodmane Jul 11, 2023
afb9ad5
Revert whitespace changes
hoodmane Jul 11, 2023
aa213e0
Call _Py_EmscriptenTrampoline_Init before setting _initialized to 1
hoodmane Jul 11, 2023
00afb90
Sort include list
hoodmane Jul 11, 2023
20ad6ab
Follow pep 7
hoodmane Jul 11, 2023
ae7584c
Merge branch 'main' into jspi-trampoline
hoodmane Aug 20, 2023
1b6b6af
Move wasm_type_reflection_available to end of struct
hoodmane Aug 20, 2023
381bb12
Address review comments
hoodmane Aug 20, 2023
58c7613
Update Include/internal/pycore_runtime.h
hoodmane Aug 21, 2023
eccf06d
Merge branch 'main' into jspi-trampoline
brettcannon Aug 21, 2023
5fff95d
Merge branch 'main' into jspi-trampoline
hoodmane Sep 1, 2023
f08a02b
Merge branch 'jspi-trampoline' of github.com:hoodmane/cpython into js…
hoodmane Sep 1, 2023
6b27584
Merge branch 'main' into jspi-trampoline
brettcannon Sep 1, 2023
22a8c75
Fix merge
hoodmane Sep 2, 2023
b40bb7b
Merge branch 'jspi-trampoline' of github.com:hoodmane/cpython into js…
hoodmane Sep 2, 2023
28b33b3
Merge branch 'main' into jspi-trampoline
brettcannon Sep 8, 2023
9ecc480
Merge branch 'main' into jspi-trampoline
brettcannon Sep 8, 2023
4aa1487
Test setting ac_cv_libatomic_needed=no
hoodmane Sep 9, 2023
a57dfdc
Set libatomic_needed=no if cross compiling
hoodmane Sep 9, 2023
6358371
Merge branch 'main' into jspi-trampoline
hoodmane Sep 11, 2023
6781a48
Merge branch 'main' into jspi-trampoline
brettcannon Sep 13, 2023
fd59df4
Merge branch 'main' into jspi-trampoline
brettcannon Sep 14, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
81 changes: 81 additions & 0 deletions Include/internal/pycore_emscripten_trampoline.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
#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
28 changes: 1 addition & 27 deletions Include/internal/pycore_object.h
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ extern "C" {

#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()
#include "pycore_runtime.h" // _PyRuntime
Expand Down Expand Up @@ -411,33 +412,6 @@ extern int _PyObject_IsInstanceDictEmpty(PyObject *);

PyAPI_FUNC(PyObject *) _PyObject_LookupSpecial(PyObject *, PyObject *);

/* C function call trampolines to mitigate bad function pointer casts.
*
* 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)
#define _PyCFunction_TrampolineCall(meth, self, args) \
_PyCFunctionWithKeywords_TrampolineCall( \
(*(PyCFunctionWithKeywords)(void(*)(void))(meth)), (self), (args), NULL)
extern PyObject* _PyCFunctionWithKeywords_TrampolineCall(
PyCFunctionWithKeywords meth, PyObject *, PyObject *, PyObject *);
#else
#define _PyCFunction_TrampolineCall(meth, self, args) \
(meth)((self), (args))
#define _PyCFunctionWithKeywords_TrampolineCall(meth, self, args, kw) \
(meth)((self), (args), (kw))
#endif // __EMSCRIPTEN__ && PY_CALL_TRAMPOLINE

#ifdef __cplusplus
}
#endif
Expand Down
5 changes: 5 additions & 0 deletions Include/internal/pycore_runtime.h
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,11 @@ typedef struct pyruntimestate {
/* Is Python fully initialized? Set to 1 by Py_Initialize() */
int initialized;
hoodmane marked this conversation as resolved.
Show resolved Hide resolved

#if defined(__EMSCRIPTEN__) && defined(PY_CALL_TRAMPOLINE)
/* Choose between trampoline based on type reflection vs based on EM_JS */
int wasm_type_reflection_available;
hoodmane marked this conversation as resolved.
Show resolved Hide resolved
#endif

/* Set by Py_FinalizeEx(). Only reset to NULL if Py_Initialize()
is called again.

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Changed the way that Emscripten call trampolines work for compatibility with
Wasm/JS Promise integration.
20 changes: 1 addition & 19 deletions Objects/descrobject.c
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
#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_object.h" // _PyObject_GC_UNTRACK()
#include "pycore_pystate.h" // _PyThreadState_GET()
#include "pycore_tuple.h" // _PyTuple_ITEMS()
Expand All @@ -16,25 +17,6 @@ class property "propertyobject *" "&PyProperty_Type"
[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)
{
Expand Down
7 changes: 0 additions & 7 deletions Objects/methodobject.c
Original file line number Diff line number Diff line change
Expand Up @@ -551,10 +551,3 @@ cfunction_call(PyObject *func, PyObject *args, PyObject *kwargs)
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
82 changes: 82 additions & 0 deletions Python/emscripten_trampoline.c
hoodmane marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
#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
3 changes: 3 additions & 0 deletions Python/pystate.c
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
#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"
#include "pycore_object.h" // _PyType_InitCache()
Expand Down Expand Up @@ -451,6 +452,8 @@ init_runtime(_PyRuntimeState *runtime,

runtime->unicode_state.ids.next_index = unicode_next_index;

_Py_EmscriptenTrampoline_Init(runtime);
hoodmane marked this conversation as resolved.
Show resolved Hide resolved
hoodmane marked this conversation as resolved.
Show resolved Hide resolved

runtime->_initialized = 1;
}

Expand Down
4 changes: 2 additions & 2 deletions configure

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions configure.ac
Original file line number Diff line number Diff line change
Expand Up @@ -4770,8 +4770,8 @@ PLATFORM_OBJS=

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])
Expand Down