packages feed

inline-python-0.3.0.0: cbits/python.c

#include <inline-python.h>
#include <stdlib.h>

#include "MachDeps.h"


// ================================================================
// Callbacks
//
// General idea: we store function pointer (haskell's FunPtr) in
// PyCapsule and use to call function. Most importantly we must
// release GIL before calling into haskell. Haskell callback will
// happen on different thread (on threaded RTS). So it'll have to
// reacquire GIL there.
// ================================================================

// Same wrapper works for METH_O and METH_NOARGS
static PyObject* callback_METH_CFunction(PyObject* self, PyObject* arg) {
    PyObject    *res;
    PyCFunction *fun = PyCapsule_GetPointer(self, NULL);
Py_BEGIN_ALLOW_THREADS
    res = (*fun)(self, arg);
Py_END_ALLOW_THREADS
    return res;
}

static PyObject* callback_METH_FASTCALL(PyObject* self, PyObject** args, Py_ssize_t nargs) {
    PyObject        *res;
    PyCFunctionFast *fun = PyCapsule_GetPointer(self, NULL);
Py_BEGIN_ALLOW_THREADS
    res = (*fun)(self, args, nargs);
Py_END_ALLOW_THREADS
    return res;
}

static void capsule_free_FunPtr(PyObject* capsule) {
    PyCFunction *fun = PyCapsule_GetPointer(capsule, NULL);
    // We call directly to haskell RTS to free FunPtr. Only question
    // is how stable is this API.
    freeHaskellFunctionPtr(*fun);
    free(fun);
}

static PyMethodDef method_METH_NOARGS = {
    .ml_name  = "[inline_python]",
    .ml_meth  = callback_METH_CFunction,
    .ml_flags = METH_NOARGS,
    .ml_doc   = "Wrapper for haskell callback"
};

static PyMethodDef method_METH_O = {
    .ml_name  = "[inline_python]",
    .ml_meth  = callback_METH_CFunction,
    .ml_flags = METH_O,
    .ml_doc   = "Wrapper for haskell callback"
};

static PyMethodDef method_METH_FASTCALL = {
    .ml_name  = "[inline_python]",
    .ml_meth  = (PyCFunction)callback_METH_FASTCALL,
    .ml_flags = METH_FASTCALL,
    .ml_doc   = "Wrapper for haskell callback"
};

PyObject *inline_py_callback_METH_NOARGS(PyCFunction fun) {
    PyCFunction *buf = malloc(sizeof(PyCFunction));
    *buf = fun;
    PyObject* self = PyCapsule_New(buf, NULL, &capsule_free_FunPtr);
    if( PyErr_Occurred() )
        return NULL;
    // Python function
    PyObject* f = PyCFunction_New(&method_METH_NOARGS, self);
    Py_DECREF(self);
    return f;
}

PyObject *inline_py_callback_METH_O(PyCFunction fun) {
    PyCFunction *buf = malloc(sizeof(PyCFunction));
    *buf = fun;
    PyObject* self = PyCapsule_New(buf, NULL, &capsule_free_FunPtr);
    if( PyErr_Occurred() )
        return NULL;
    // Python function
    PyObject* f = PyCFunction_New(&method_METH_O, self);
    Py_DECREF(self);
    return f;
}

PyObject *inline_py_callback_METH_FASTCALL(PyCFunctionFast fun) {
    PyCFunctionFast *buf = malloc(sizeof(PyCFunctionFast));
    *buf = fun;
    PyObject* self = PyCapsule_New(buf, NULL, &capsule_free_FunPtr);
    if( PyErr_Occurred() )
        return NULL;
    // Python function
    PyObject* f = PyCFunction_New(&method_METH_FASTCALL, self);
    Py_DECREF(self);
    return f;
}


// ================================================================
// Marshalling
// ================================================================

int inline_py_unpack_iterable(PyObject *iterable, int n, PyObject **out) {
    // Initialize iterator. If object is not an iterable we treat this
    // as not an exception but as a conversion failure
    PyObject* iter = PyObject_GetIter( iterable );
    if( PyErr_Occurred() ) {
        PyErr_Clear();
        return -1;
    }
    if( !PyIter_Check(iter) ) {
        goto err_iter;
    }
    // Fill out with NULL. This way we can call XDECREF on them
    for(int i = 0; i < n; i++) {
        out[i] = NULL;
    }
    // Fill elements
    for(int i = 0; i < n; i++) {
        out[i] = PyIter_Next(iter);
        if( NULL==out[i] ) {
            goto err_elem;
        }
    }
    // End of iteration
    PyObject* end = PyIter_Next(iter);
    if( NULL != end || PyErr_Occurred() ) {
        goto err_end;
    }
    return 0;
    //----------------------------------------
err_end:
    Py_XDECREF(end);
err_elem:
    for(int i = 0; i < n; i++) {
        Py_XDECREF(out[i]);
    }
err_iter:
    Py_DECREF(iter);
    return -1;
}


PyObject* inline_py_Integer_ToPy(
    void*  buf,
    size_t size,
    int    sign
    )
{
    PyObject* num =
#if PY_MINOR_VERSION < 13
        _PyLong_FromByteArray(buf, size,
                              1, // Little endian
                              0  // Unsigned
        );
#else
        PyLong_FromNativeBytes(buf, size,
                               Py_ASNATIVEBYTES_LITTLE_ENDIAN |
                               Py_ASNATIVEBYTES_UNSIGNED_BUFFER
        );
#endif
    if( sign ) {
        PyObject* neg = PyNumber_Negative(num);
        Py_DECREF(num);
        return neg;
    } else {
        return num;
    }
}


ssize_t inline_py_Long_ByteSize(PyObject* p) {
    // See NOTE: [Integer encoding/decoding]
    //
    // PyLong_AsNativeBytes allows to compute buffer size but it does
    // so according to python's memory layout
#if WORD_SIZE_IN_BITS == 32
    const int shiftW = 2;
#elif WORD_SIZE_IN_BITS == 64
    const int shiftW = 3;
#else
#error "Something wrong with MachDeps.h"
#endif
    const int     shift = shiftW + 3;
    const ssize_t mask  = (1<<shift) - 1;
    const ssize_t bits  = _PyLong_NumBits(p);
    if( bits & mask ) {
        return ((bits >> shift) + 1) << shiftW;
    } else {
        return (bits >> shift) << shiftW;
    }
}

void inline_py_Integer_FromPy(
    PyObject* p,
    void*     buf,
    size_t    size
    )
{
    // N.B. _PyLong_AsByteArray changed signature in 3.13
#if PY_MINOR_VERSION < 13
    _PyLong_AsByteArray((PyLongObject*)p, buf, size,
                        1, // little_endian
                        0  // is_signed
        );
#else
    PyLong_AsNativeBytes(p, buf, size, -1);
#endif
}


static Py_tss_t key_py_async = Py_tss_NEEDS_INIT;

void inline_py_init_state(void *stack) {
    int r = PyThread_tss_set(&key_py_async, stack);
    if( 0 != r ) {
        fprintf(stderr, "inline-python: fatal error: setting thread local storage failed\n");
        exit(1);
    }
}

void inline_py_free_state(void) {
    int r = PyThread_tss_set(&key_py_async, NULL);
    if( 0 != r ) {
        fprintf(stderr, "inline-python: fatal error: setting thread local storage failed\n");
        exit(1);
    }
}

void* inline_py_get_state(void) {
    return PyThread_tss_get(&key_py_async);
}


void inline_py_initialize(void) {
    int r = PyThread_tss_create(&key_py_async);
    if( 0 != r ) {
        fprintf(stderr, "inline-python: fatal error: Failed to initialized thread local storage\n");
        exit(1);
    }
}

void inline_py_clear_error(void) {
    static PyObject* globals = NULL;
    static PyObject* locals  = NULL;
    static PyObject* code    = NULL;
    if( NULL == code ) {
        globals = PyDict_New();
        locals  = PyDict_New();
        code    = Py_CompileString("None", "<interactive>", Py_eval_input);
    }
    // None is immortal. No need to decrement counter
    PyEval_EvalCode(code, globals, locals);
    PyErr_Clear();
}


// ================================================================
// inline_python module
// ================================================================

PyObject* (*inline_py_haskell_error_repr)(void*);
PyObject* (*inline_py_haskell_error_tyrepr)(void*);

PyObject* inline_py_AsyncCancelled() {
    static PyObject* AsyncCancelled = 0;
    if( AsyncCancelled == 0 ) {
        AsyncCancelled = PyErr_NewException("inline_python.AsyncCancelled", PyExc_BaseException, 0);
    }
    return AsyncCancelled;
}

typedef struct {
    PyBaseExceptionObject obj;
    void *exception_stableptr;
} HaskellError;

static void haskell_error_dealloc(PyObject *op) {
    HaskellError *self = (HaskellError*) op;
    hs_free_stable_ptr(self->exception_stableptr);
    Py_TYPE(self)->tp_free(self);
}

static PyObject* haskell_error_repr(PyObject *op) {
    HaskellError *self = (HaskellError*) op;
    PyObject* exc_repr = inline_py_haskell_error_repr(self->exception_stableptr);
    PyObject* exc_ty   = inline_py_haskell_error_tyrepr(self->exception_stableptr);
    PyObject* repr     = PyUnicode_FromFormat("<inline_python.HaskellError: %S: %S>", exc_ty, exc_repr);
    Py_DECREF(exc_repr);
    Py_DECREF(exc_ty);
    return repr;
}

static PyTypeObject HaskellError_Type = {
    PyVarObject_HEAD_INIT(NULL, 0)
    .tp_name      = "inline_python.HaskellError",
    .tp_basicsize = sizeof(HaskellError),
    .tp_flags     = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_DISALLOW_INSTANTIATION,
    .tp_doc       = PyDoc_STR("Wrapper for a haskell exception object"),
    .tp_dealloc   = haskell_error_dealloc,
    .tp_repr      = haskell_error_repr
};


PyObject* inline_py_HaskellError() {
    static int initialized = 0;
    if( 0 == initialized ) {
        if( PyType_Ready(&HaskellError_Type) != 0) {
            // It should success always and we don't have any reasonable
            // way of handing error.
            exit(1);
        }
        initialized = 1;
    }
    return (PyObject*)&HaskellError_Type;
}

PyObject* inline_py_HaskellError_create(void* exc_ptr) {
    PyTypeObject *base = HaskellError_Type.tp_base;
    PyObject *args     = PyTuple_New(0);
    // Call __new__
    PyObject *obj = base->tp_new(&HaskellError_Type, args, NULL);
    if( !obj ) {
        goto err;
    }
    // Call __init__
    if( 0 != base->tp_init(obj, args, NULL) ) {
        goto err;
    }
    // Set custom fields
    HaskellError* h_err = (HaskellError*)obj;
    h_err->exception_stableptr = exc_ptr;
    Py_DECREF(args);
    return obj;
err:
    Py_DECREF(args);
    return NULL;
}

void* inline_py_HaskellError_get_stableptr(PyObject* err) {
    HaskellError *h_err = (HaskellError*) err;
    return h_err->exception_stableptr;
}



static PyMethodDef inline_python_methods[] = {
    {NULL, NULL, 0, NULL}
};

static int inline_python_module_exec(PyObject *m) {
    // Initialize module only once
    static int initialized = 0;
    if( initialized ) {
        return 0;
    }
    initialized = 1;
    //
    if (PyModule_AddObjectRef(m, "AsyncCancelled", inline_py_AsyncCancelled()) < 0) {
        return -1;
    }
    //
    HaskellError_Type.tp_base = (PyTypeObject*)PyExc_Exception;
    HaskellError_Type.tp_new  = NULL;
    if (PyModule_AddObjectRef(m, "HaskellError", inline_py_HaskellError()) < 0) {
        return -1;
    }
    return 0;
}

static PyModuleDef_Slot inline_python_module_slots[] = {
    {Py_mod_exec, inline_python_module_exec},
    {0, NULL}
};

static struct PyModuleDef inline_python_module = {
    .m_base    = PyModuleDef_HEAD_INIT,
    .m_name    = "inline_python",
    .m_size    = 0,
    .m_slots   = inline_python_module_slots,
    .m_methods = inline_python_methods,
};

PyMODINIT_FUNC PyInit_inline_python(void)
{
    return PyModuleDef_Init(&inline_python_module);
}