packages feed

inline-python-0.2.0.1: 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
}