mirror of https://github.com/python/cpython.git
216 lines
8.3 KiB
C
216 lines
8.3 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
|
|
// )
|
|
// )
|
|
addOnPreRun(() => {
|
|
// 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
|
|
]);
|
|
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 + 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
|