summaryrefslogtreecommitdiffstats
path: root/Python/emscripten_trampoline.c
blob: 2f9648a12ef2e441745a6f5e84d1b1df3815a380 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
#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 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 $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/4 + 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*);

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