/* -*-c-*- */
#define PY_SSIZE_T_CLEAN
#include <Python.h>

#define NPY_NO_DEPRECATED_API NPY_API_VERSION
#include "numpy/arrayobject.h"
#include "numpy/arrayscalars.h"
#include "numpy/npy_math.h"
#include "numpy/halffloat.h"
#include "common.h"
#include "npy_argparse.h"
#include "mem_overlap.h"
#include "npy_extint128.h"
#include "array_method.h"
#include "npy_hashtable.h"
#include "dtypemeta.h"

#if defined(MS_WIN32) || defined(__CYGWIN__)
#define EXPORT(x) __declspec(dllexport) x
#else
#define EXPORT(x) x
#endif

#define ARRAY_SIZE(a) (sizeof(a)/sizeof(a[0]))


static PyObject *
argparse_example_function(PyObject *NPY_UNUSED(mod),
        PyObject *const *args, Py_ssize_t len_args, PyObject *kwnames)
{
    NPY_PREPARE_ARGPARSER;
    int arg1;
    PyObject *arg2, *arg3, *arg4;
    if (npy_parse_arguments("func", args, len_args, kwnames,
            "", &PyArray_PythonPyIntFromInt, &arg1,
            "arg2", NULL, &arg2,
            "|arg3", NULL, &arg3,
            "$arg3", NULL, &arg4,
            NULL, NULL, NULL) < 0) {
        return NULL;
    }
    Py_RETURN_NONE;
}

/*
 *  Tests that argparse cache creation is thread-safe. *must* be called only
 *  by the python-level test_thread_safe_argparse_cache function, otherwise
 *  the cache might be created before the test to make sure cache creation is
 *  thread-safe runs
 */
static PyObject *
threaded_argparse_example_function(PyObject *NPY_UNUSED(mod),
        PyObject *const *args, Py_ssize_t len_args, PyObject *kwnames)
{
    NPY_PREPARE_ARGPARSER;
    int arg1;
    PyObject *arg2;
    if (npy_parse_arguments("thread_func", args, len_args, kwnames,
            "$arg1", &PyArray_PythonPyIntFromInt, &arg1,
            "$arg2", NULL, &arg2,
            NULL, NULL, NULL) < 0) {
        return NULL;
    }
    Py_RETURN_NONE;
}

/* test PyArray_IsPythonScalar, before including private py3 compat header */
static PyObject *
IsPythonScalar(PyObject * dummy, PyObject *args)
{
    PyObject *arg = NULL;
    if (!PyArg_ParseTuple(args, "O", &arg)) {
        return NULL;
    }
    if (PyArray_IsPythonScalar(arg)) {
        Py_RETURN_TRUE;
    }
    else {
        Py_RETURN_FALSE;
    }
}




/** Function to test calling via ctypes */
EXPORT(void*) forward_pointer(void *x)
{
    return x;
}

/*
 * TODO:
 *  - Handle mode
 */

/**begin repeat
 * #name = double, int#
 * #type = npy_double, npy_int#
 * #typenum = NPY_DOUBLE, NPY_INT#
 */
static int copy_@name@(PyArrayIterObject *itx, PyArrayNeighborhoodIterObject *niterx,
        npy_intp const *bounds,
        PyObject **out)
{
    npy_intp i, j;
    @type@ *ptr;
    npy_intp odims[NPY_MAXDIMS_LEGACY_ITERS];
    PyArrayObject *aout;

    /*
     * For each point in itx, copy the current neighborhood into an array which
     * is appended at the output list
     */
    for (i = itx->index; i < itx->size; ++i) {
        PyArrayNeighborhoodIter_Reset(niterx);

        for (j = 0; j < PyArray_NDIM(itx->ao); ++j) {
            odims[j] = bounds[2 * j + 1] - bounds[2 * j] + 1;
        }
        aout = (PyArrayObject*)PyArray_SimpleNew(
                                PyArray_NDIM(itx->ao), odims, @typenum@);
        if (aout == NULL) {
            return -1;
        }

        ptr = (@type@*)PyArray_DATA(aout);

        for (j = 0; j < niterx->size; ++j) {
            *ptr = *((@type@*)niterx->dataptr);
            PyArrayNeighborhoodIter_Next(niterx);
            ptr += 1;
        }

        PyList_Append(*out, (PyObject*)aout);
        Py_DECREF(aout);
        PyArray_ITER_NEXT(itx);
    }

    return 0;
}
/**end repeat**/

static int copy_object(PyArrayIterObject *itx, PyArrayNeighborhoodIterObject *niterx,
        npy_intp const *bounds,
        PyObject **out)
{
    npy_intp i, j;
    npy_intp odims[NPY_MAXDIMS_LEGACY_ITERS];
    PyArrayObject *aout;
    PyArray_CopySwapFunc *copyswap = PyDataType_GetArrFuncs(PyArray_DESCR(itx->ao))->copyswap;
    npy_int itemsize = PyArray_ITEMSIZE(itx->ao);

    /*
     * For each point in itx, copy the current neighborhood into an array which
     * is appended at the output list
     */
    for (i = itx->index; i < itx->size; ++i) {
        PyArrayNeighborhoodIter_Reset(niterx);

        for (j = 0; j < PyArray_NDIM(itx->ao); ++j) {
            odims[j] = bounds[2 * j + 1] - bounds[2 * j] + 1;
        }
        aout = (PyArrayObject*)PyArray_SimpleNew(PyArray_NDIM(itx->ao), odims, NPY_OBJECT);
        if (aout == NULL) {
            return -1;
        }

        for (j = 0; j < niterx->size; ++j) {
            copyswap(PyArray_BYTES(aout) + j * itemsize, niterx->dataptr, 0, NULL);
            PyArrayNeighborhoodIter_Next(niterx);
        }

        PyList_Append(*out, (PyObject*)aout);
        Py_DECREF(aout);
        PyArray_ITER_NEXT(itx);
    }

    return 0;
}

static PyObject*
test_neighborhood_iterator(PyObject* NPY_UNUSED(self), PyObject* args)
{
    PyObject *x, *fill, *out, *b;
    PyArrayObject *ax, *afill;
    PyArrayIterObject *itx;
    int i, typenum, mode, st;
    Py_ssize_t idxstart = 0;
    npy_intp bounds[NPY_MAXDIMS_LEGACY_ITERS*2];
    PyArrayNeighborhoodIterObject *niterx;

    if (!PyArg_ParseTuple(args, "OOOi|n", &x, &b, &fill, &mode, &idxstart)) {
        return NULL;
    }

    if (!PySequence_Check(b)) {
        return NULL;
    }

    typenum = PyArray_ObjectType(x, NPY_NOTYPE);
    if (typenum == NPY_NOTYPE) {
        return NULL;
    }
    typenum = PyArray_ObjectType(fill, typenum);
    if (typenum == NPY_NOTYPE) {
        return NULL;
    }

    ax = (PyArrayObject*)PyArray_FromObject(x, typenum, 1, 10);
    if (ax == NULL) {
        return NULL;
    }
    if (PySequence_Size(b) != 2 * PyArray_NDIM(ax)) {
        PyErr_SetString(PyExc_ValueError,
                "bounds sequence size not compatible with x input");
        goto clean_ax;
    }

    out = PyList_New(0);
    if (out == NULL) {
        goto clean_ax;
    }

    itx = (PyArrayIterObject*)PyArray_IterNew(x);
    if (itx == NULL) {
        goto clean_out;
    }

    /* Compute boundaries for the neighborhood iterator */
    for (i = 0; i < 2 * PyArray_NDIM(ax); ++i) {
        PyObject* bound;

        bound = PySequence_GetItem(b, i);
        if (bound == NULL) {
            goto clean_itx;
        }
        /* PyLong_AsSsize checks for PyLong */
        bounds[i] = PyLong_AsSsize_t(bound);
        if (error_converting(bounds[i])) {
            PyErr_Clear();
            PyErr_SetString(PyExc_ValueError,
                    "bound is invalid");
            Py_DECREF(bound);
            goto clean_itx;
        }
        Py_DECREF(bound);
    }

    /* Create the neighborhood iterator */
    afill = NULL;
    if (mode == NPY_NEIGHBORHOOD_ITER_CONSTANT_PADDING) {
            afill = (PyArrayObject *)PyArray_FromObject(fill, typenum, 0, 0);
            if (afill == NULL) {
            goto clean_itx;
        }
    }

    if (idxstart >= itx->size) {
        PyErr_SetString(PyExc_ValueError,
                "start index not compatible with x input");
        goto clean_itx;
    }

    niterx = (PyArrayNeighborhoodIterObject*)PyArray_NeighborhoodIterNew(
                    (PyArrayIterObject*)itx, bounds, mode, afill);
    if (niterx == NULL) {
        goto clean_afill;
    }

    PyArray_ITER_GOTO1D((PyArrayIterObject*)itx, idxstart);

    switch (typenum) {
        case NPY_OBJECT:
            st = copy_object(itx, niterx, bounds, &out);
            break;
        case NPY_INT:
            st = copy_int(itx, niterx, bounds, &out);
            break;
        case NPY_DOUBLE:
            st = copy_double(itx, niterx, bounds, &out);
            break;
        default:
            PyErr_SetString(PyExc_ValueError,
                    "Type not supported");
            goto clean_niterx;
    }

    if (st) {
        goto clean_niterx;
    }

    Py_DECREF(niterx);
    Py_XDECREF(afill);
    Py_DECREF(itx);

    Py_DECREF(ax);

    return out;

clean_niterx:
    Py_DECREF(niterx);
clean_afill:
    Py_XDECREF(afill);
clean_itx:
    Py_DECREF(itx);
clean_out:
    Py_DECREF(out);
clean_ax:
    Py_DECREF(ax);
    return NULL;
}

static int
copy_double_double(PyArrayNeighborhoodIterObject *itx,
        PyArrayNeighborhoodIterObject *niterx,
        npy_intp const *bounds,
        PyObject **out)
{
    npy_intp i, j;
    double *ptr;
    npy_intp odims[NPY_MAXDIMS_LEGACY_ITERS];
    PyArrayObject *aout;

    /*
     * For each point in itx, copy the current neighborhood into an array which
     * is appended at the output list
     */
    PyArrayNeighborhoodIter_Reset(itx);
    for (i = 0; i < itx->size; ++i) {
        for (j = 0; j < PyArray_NDIM(itx->ao); ++j) {
            odims[j] = bounds[2 * j + 1] - bounds[2 * j] + 1;
        }
        aout = (PyArrayObject*)PyArray_SimpleNew(
                            PyArray_NDIM(itx->ao), odims, NPY_DOUBLE);
        if (aout == NULL) {
            return -1;
        }

        ptr = (double*)PyArray_DATA(aout);

        PyArrayNeighborhoodIter_Reset(niterx);
        for (j = 0; j < niterx->size; ++j) {
            *ptr = *((double*)niterx->dataptr);
            ptr += 1;
            PyArrayNeighborhoodIter_Next(niterx);
        }
        PyList_Append(*out, (PyObject*)aout);
        Py_DECREF(aout);
        PyArrayNeighborhoodIter_Next(itx);
    }
    return 0;
}

static PyObject*
test_neighborhood_iterator_oob(PyObject* NPY_UNUSED(self), PyObject* args)
{
    PyObject *x, *out, *b1, *b2;
    PyArrayObject *ax;
    PyArrayIterObject *itx;
    int i, typenum, mode1, mode2, st;
    npy_intp bounds[NPY_MAXDIMS_LEGACY_ITERS*2];
    PyArrayNeighborhoodIterObject *niterx1, *niterx2;

    if (!PyArg_ParseTuple(args, "OOiOi", &x, &b1, &mode1, &b2, &mode2)) {
        return NULL;
    }

    if (!PySequence_Check(b1) || !PySequence_Check(b2)) {
        return NULL;
    }

    typenum = PyArray_ObjectType(x, NPY_NOTYPE);
    if (typenum == NPY_NOTYPE) {
        return NULL;
    }

    ax = (PyArrayObject*)PyArray_FromObject(x, typenum, 1, 10);
    if (ax == NULL) {
        return NULL;
    }
    if (PyArray_NDIM(ax) > NPY_MAXDIMS_LEGACY_ITERS) {
        PyErr_SetString(PyExc_TypeError, "too many dimensions.");
        goto clean_ax;
    }
    if (PySequence_Size(b1) != 2 * PyArray_NDIM(ax)) {
        PyErr_SetString(PyExc_ValueError,
                "bounds sequence 1 size not compatible with x input");
        goto clean_ax;
    }
    if (PySequence_Size(b2) != 2 * PyArray_NDIM(ax)) {
        PyErr_SetString(PyExc_ValueError,
                "bounds sequence 2 size not compatible with x input");
        goto clean_ax;
    }

    out = PyList_New(0);
    if (out == NULL) {
        goto clean_ax;
    }

    itx = (PyArrayIterObject*)PyArray_IterNew(x);
    if (itx == NULL) {
        goto clean_out;
    }

    /* Compute boundaries for the neighborhood iterator */
    for (i = 0; i < 2 * PyArray_NDIM(ax); ++i) {
        PyObject* bound;

        bound = PySequence_GetItem(b1, i);
        if (bound == NULL) {
            goto clean_itx;
        }
        /* PyLong_AsSsize checks for PyLong */
        bounds[i] = PyLong_AsSsize_t(bound);
        if (error_converting(bounds[i])) {
            PyErr_Clear();
            PyErr_SetString(PyExc_ValueError,
                    "bound is invalid");
            Py_DECREF(bound);
            goto clean_itx;
        }
        Py_DECREF(bound);
    }

    /* Create the neighborhood iterator */
    niterx1 = (PyArrayNeighborhoodIterObject*)PyArray_NeighborhoodIterNew(
                    (PyArrayIterObject*)itx, bounds,
                    mode1, NULL);
    if (niterx1 == NULL) {
        goto clean_out;
    }

    for (i = 0; i < 2 * PyArray_NDIM(ax); ++i) {
        PyObject* bound;

        bound = PySequence_GetItem(b2, i);
        if (bound == NULL) {
            goto clean_itx;
        }
        /* PyLong_AsSsize checks for PyLong */
        bounds[i] = PyLong_AsSsize_t(bound);
        if (error_converting(bounds[i])) {
            PyErr_Clear();
            PyErr_SetString(PyExc_ValueError,
                    "bound is invalid");
            Py_DECREF(bound);
            goto clean_itx;
        }
        Py_DECREF(bound);
    }

    niterx2 = (PyArrayNeighborhoodIterObject*)PyArray_NeighborhoodIterNew(
                    (PyArrayIterObject*)niterx1, bounds,
                    mode2, NULL);
    if (niterx2 == NULL) {
        goto clean_niterx1;
    }

    switch (typenum) {
        case NPY_DOUBLE:
            st = copy_double_double(niterx1, niterx2, bounds, &out);
            break;
        default:
            PyErr_SetString(PyExc_ValueError,
                    "Type not supported");
            goto clean_niterx2;
    }

    if (st) {
        goto clean_niterx2;
    }

    Py_DECREF(niterx2);
    Py_DECREF(niterx1);
    Py_DECREF(itx);
    Py_DECREF(ax);
    return out;

clean_niterx2:
    Py_DECREF(niterx2);
clean_niterx1:
    Py_DECREF(niterx1);
clean_itx:
    Py_DECREF(itx);
clean_out:
    Py_DECREF(out);
clean_ax:
    Py_DECREF(ax);
    return NULL;
}


/*
 * Helper to test fromstring of 0 terminated strings, as the C-API supports
 * the -1 length identifier.
 */
static PyObject *
fromstring_null_term_c_api(PyObject *dummy, PyObject *byte_obj)
{
    char *string;

    string = PyBytes_AsString(byte_obj);
    if (string == NULL) {
        return NULL;
    }
    return PyArray_FromString(string, -1, NULL, -1, " ");
}


/*
 * Create a custom field dtype from an existing void one (and test some errors).
 * The dtypes created by this function may be not be usable (or even crash
 * while using).
 */
static PyObject *
create_custom_field_dtype(PyObject *NPY_UNUSED(mod), PyObject *args)
{
    PyArray_DescrProto proto;
    _PyArray_LegacyDescr *dtype;  /* Is checked for void, so legacy is OK */
    PyTypeObject *scalar_type;
    int error_path;

    if (!PyArg_ParseTuple(args, "O!O!i",
            &PyArrayDescr_Type, &dtype,
            &PyType_Type, &scalar_type,
            &error_path)) {
        return NULL;
    }
    /* check that the result should be more or less valid */
    if (dtype->type_num != NPY_VOID || dtype->fields == NULL ||
            !PyDict_CheckExact(dtype->fields) ||
            PyTuple_Size(dtype->names) != 1 ||
            !PyDataType_REFCHK((PyArray_Descr *)dtype) ||
            dtype->elsize != sizeof(PyObject *)) {
        PyErr_SetString(PyExc_ValueError,
                "Bad dtype passed to test function, must be an object "
                "containing void with a single field.");
        return NULL;
    }

    /* Set all fields, mostly copying them from the passed in dtype: */
    Py_SET_TYPE(&proto, Py_TYPE(dtype));
    proto.typeobj = scalar_type;
    proto.kind = dtype->kind;
    proto.type = dtype->type;
    proto.byteorder = dtype->byteorder;
    proto.flags = dtype->flags;
    proto.type_num = dtype->type_num;
    proto.elsize = dtype->elsize;
    proto.alignment = dtype->alignment;
    proto.subarray = dtype->subarray;
    proto.fields = dtype->fields;
    proto.names = dtype->names;
    proto.f = PyDataType_GetArrFuncs((PyArray_Descr *)dtype);
    proto.metadata = dtype->metadata;
    proto.c_metadata = dtype->c_metadata;

    if (error_path == 1) {
        /* Test that we reject this, if fields was not already set */
        proto.fields = NULL;
    }
    else if (error_path == 2) {
        /*
         * Test that we reject this if the type is not set to something that
         * we are pretty sure can be safely replaced.
         */
        Py_SET_TYPE(&proto, scalar_type);
    }
    else if (error_path != 0) {
        PyErr_SetString(PyExc_ValueError,
                "invalid error argument to test function.");
    }
    int new_typenum = PyArray_RegisterDataType(&proto);
    if (new_typenum < 0) {
        return NULL;
    }

    return (PyObject *)PyArray_DescrFromType(new_typenum);
}


PyObject *
corrupt_or_fix_bufferinfo(PyObject *dummy, PyObject *obj)
{
    void **buffer_info_ptr;
    if (PyArray_Check(obj)) {
        buffer_info_ptr = &((PyArrayObject_fields *)obj)->_buffer_info;
    }
    else if (PyArray_IsScalar(obj, Void)) {
        buffer_info_ptr = &((PyVoidScalarObject *)obj)->_buffer_info;
    }
    else {
        PyErr_SetString(PyExc_TypeError,
                "argument must be an array or void scalar");
        return NULL;
    }
    if (*buffer_info_ptr == NULL) {
        /* set to an invalid value (as a subclass might accidentally) */
        *buffer_info_ptr = obj;
        assert(((uintptr_t)obj & 7) == 0);
    }
    else if (*buffer_info_ptr == obj) {
        /* Reset to a NULL (good value) */
        *buffer_info_ptr = NULL;
    }
    else {
        PyErr_SetString(PyExc_TypeError,
                "buffer was already exported, this test doesn't support that");
        return NULL;
    }
    Py_RETURN_NONE;
}


/* check no elison for avoided increfs */
static PyObject *
incref_elide(PyObject *dummy, PyObject *args)
{
    PyObject *arg = NULL, *res, *tup;
    if (!PyArg_ParseTuple(args, "O", &arg)) {
        return NULL;
    }

    /* refcount 1 array but should not be elided */
    arg = PyArray_NewCopy((PyArrayObject*)arg, NPY_KEEPORDER);
    res = PyNumber_Add(arg, arg);

    /* return original copy, should be equal to input */
    tup = PyTuple_Pack(2, arg, res);
    Py_DECREF(arg);
    Py_DECREF(res);
    return tup;
}

/* check no elison for get from list without incref */
static PyObject *
incref_elide_l(PyObject *dummy, PyObject *args)
{
    PyObject *arg = NULL, *r, *res;
    if (!PyArg_ParseTuple(args, "O", &arg)) {
        return NULL;
    }
    /* get item without increasing refcount, item may still be on the python
     * stack but above the inaccessible top */
    r = PyList_GetItem(arg, 4);
    res = PyNumber_Add(r, r);

    return res;
}

/* used to test NPY_CHAR usage raises an error */
static PyObject*
npy_char_deprecation(PyObject* NPY_UNUSED(self), PyObject* NPY_UNUSED(args))
{
    PyArray_Descr * descr = PyArray_DescrFromType(NPY_CHAR);
    return (PyObject *)descr;
}

/* used to create array with WRITEBACKIFCOPY flag */
static PyObject*
npy_create_writebackifcopy(PyObject* NPY_UNUSED(self), PyObject* args)
{
    int flags;
    PyObject* array;
    if (!PyArray_Check(args)) {
        PyErr_SetString(PyExc_TypeError, "test needs ndarray input");
        return NULL;
    }
    flags = NPY_ARRAY_CARRAY | NPY_ARRAY_WRITEBACKIFCOPY;
    array = PyArray_FromArray((PyArrayObject*)args, NULL, flags);
    if (array == NULL)
        return NULL;
    return array;
}

/* used to test WRITEBACKIFCOPY without resolution emits runtime warning */
static PyObject*
npy_abuse_writebackifcopy(PyObject* NPY_UNUSED(self), PyObject* args)
{
    int flags;
    PyObject* array;
    if (!PyArray_Check(args)) {
        PyErr_SetString(PyExc_TypeError, "test needs ndarray input");
        return NULL;
    }
    flags = NPY_ARRAY_CARRAY | NPY_ARRAY_WRITEBACKIFCOPY;
    array = PyArray_FromArray((PyArrayObject*)args, NULL, flags);
    if (array == NULL)
        return NULL;
    Py_DECREF(array); /* calls array_dealloc even on PyPy */
    Py_RETURN_NONE;
}

/* resolve WRITEBACKIFCOPY */
static PyObject*
npy_resolve(PyObject* NPY_UNUSED(self), PyObject* args)
{
    if (!PyArray_Check(args)) {
        PyErr_SetString(PyExc_TypeError, "test needs ndarray input");
        return NULL;
    }
    PyArray_ResolveWritebackIfCopy((PyArrayObject*)args);
    Py_RETURN_NONE;
}

/* resolve WRITEBACKIFCOPY */
static PyObject*
npy_discard(PyObject* NPY_UNUSED(self), PyObject* args)
{
    if (!PyArray_Check(args)) {
        PyErr_SetString(PyExc_TypeError, "test needs ndarray input");
        return NULL;
    }
    PyArray_DiscardWritebackIfCopy((PyArrayObject*)args);
    Py_RETURN_NONE;
}

/*
 * Create python string from a FLAG and or the corresponding PyBuf flag
 * for the use in get_buffer_info.
 */
#define GET_PYBUF_FLAG(FLAG)                                        \
    buf_flag = PyUnicode_FromString(#FLAG);                         \
    flag_matches = PyObject_RichCompareBool(buf_flag, tmp, Py_EQ);  \
    Py_DECREF(buf_flag);                                            \
    if (flag_matches == 1) {                                        \
        Py_DECREF(tmp);                                             \
        flags |= PyBUF_##FLAG;                                      \
        continue;                                                   \
    }                                                               \
    else if (flag_matches == -1) {                                  \
        Py_DECREF(tmp);                                             \
        return NULL;                                                \
    }


/*
 * Get information for a buffer through PyBuf_GetBuffer with the
 * corresponding flags or'ed. Note that the python caller has to
 * make sure that or'ing those flags actually makes sense.
 * More information should probably be returned for future tests.
 */
static PyObject *
get_buffer_info(PyObject *NPY_UNUSED(self), PyObject *args)
{
    PyObject *buffer_obj, *pyflags;
    PyObject *tmp, *buf_flag;
    Py_buffer buffer;
    PyObject *shape, *strides;
    Py_ssize_t i, n;
    int flag_matches;
    int flags = 0;

    if (!PyArg_ParseTuple(args, "OO", &buffer_obj, &pyflags)) {
        return NULL;
    }

    n = PySequence_Length(pyflags);
    if (n < 0) {
        return NULL;
    }

    for (i=0; i < n; i++) {
        tmp = PySequence_GetItem(pyflags, i);
        if (tmp == NULL) {
            return NULL;
        }

        GET_PYBUF_FLAG(SIMPLE);
        GET_PYBUF_FLAG(WRITABLE);
        GET_PYBUF_FLAG(STRIDES);
        GET_PYBUF_FLAG(ND);
        GET_PYBUF_FLAG(C_CONTIGUOUS);
        GET_PYBUF_FLAG(F_CONTIGUOUS);
        GET_PYBUF_FLAG(ANY_CONTIGUOUS);
        GET_PYBUF_FLAG(INDIRECT);
        GET_PYBUF_FLAG(FORMAT);
        GET_PYBUF_FLAG(STRIDED);
        GET_PYBUF_FLAG(STRIDED_RO);
        GET_PYBUF_FLAG(RECORDS);
        GET_PYBUF_FLAG(RECORDS_RO);
        GET_PYBUF_FLAG(FULL);
        GET_PYBUF_FLAG(FULL_RO);
        GET_PYBUF_FLAG(CONTIG);
        GET_PYBUF_FLAG(CONTIG_RO);

        Py_DECREF(tmp);

        /* One of the flags must match */
        PyErr_SetString(PyExc_ValueError, "invalid flag used.");
        return NULL;
    }

    if (PyObject_GetBuffer(buffer_obj, &buffer, flags) < 0) {
        return NULL;
    }

    if (buffer.shape == NULL) {
        Py_INCREF(Py_None);
        shape = Py_None;
    }
    else {
        shape = PyTuple_New(buffer.ndim);
        for (i=0; i < buffer.ndim; i++) {
            PyTuple_SET_ITEM(shape, i, PyLong_FromSsize_t(buffer.shape[i]));
        }
    }

    if (buffer.strides == NULL) {
        Py_INCREF(Py_None);
        strides = Py_None;
    }
    else {
        strides = PyTuple_New(buffer.ndim);
        for (i=0; i < buffer.ndim; i++) {
            PyTuple_SET_ITEM(strides, i, PyLong_FromSsize_t(buffer.strides[i]));
        }
    }

    PyBuffer_Release(&buffer);
    return Py_BuildValue("(NN)", shape, strides);
}

#undef GET_PYBUF_FLAG

/*
 * Return a new array object wrapping existing C-allocated (dummy) data.
 * Such an array does not own its data (must not free it), but because it
 * wraps C data, it also has no base object. Used to test arr.flags.writeable
 * setting behaviour.
 */
static PyObject*
get_c_wrapping_array(PyObject* NPY_UNUSED(self), PyObject* arg)
{
    int writeable, flags;
    PyArray_Descr *descr;
    npy_intp zero = 0;

    writeable = PyObject_IsTrue(arg);
    if (error_converting(writeable)) {
        return NULL;
    }

    flags = writeable ? NPY_ARRAY_WRITEABLE : 0;
    /* Create an empty array (which points to a random place) */
    descr =  PyArray_DescrNewFromType(NPY_INTP);
    return PyArray_NewFromDescr(&PyArray_Type, descr,
                                1, &zero, NULL, &zero, flags, NULL);
}


static PyObject *
get_all_cast_information(PyObject *NPY_UNUSED(mod), PyObject *NPY_UNUSED(args))
{
    PyObject *result = PyList_New(0);
    if (result == NULL) {
        return NULL;
    }
    PyObject *classes = PyObject_CallMethod(
            (PyObject *)&PyArrayDescr_Type, "__subclasses__", "");
    if (classes == NULL) {
        goto fail;
    }
    Py_SETREF(classes, PySequence_Fast(classes, NULL));
    if (classes == NULL) {
        goto fail;
    }

    Py_ssize_t nclass = PySequence_Length(classes);
    for (Py_ssize_t  i = 0; i < nclass; i++) {
        PyArray_DTypeMeta *from_dtype = (
                (PyArray_DTypeMeta *)PySequence_Fast_GET_ITEM(classes, i));
        if (NPY_DT_is_abstract(from_dtype)) {
            /*
             * TODO: In principle probably needs to recursively check this,
             *       also we may allow casts to abstract dtypes at some point.
             */
            continue;
        }

        PyObject *to_dtype, *cast_obj;
        Py_ssize_t pos = 0;

        while (PyDict_Next(NPY_DT_SLOTS(from_dtype)->castingimpls,
                           &pos, &to_dtype, &cast_obj)) {
            if (cast_obj == Py_None) {
                continue;
            }
            PyArrayMethodObject *cast = (PyArrayMethodObject *)cast_obj;

            /* Pass some information about this cast out! */
            PyObject *cast_info = Py_BuildValue("{sOsOsisisisisiss}",
                    "from", from_dtype,
                    "to", to_dtype,
                    "legacy", (cast->name != NULL &&
                               strncmp(cast->name, "legacy_", 7) == 0),
                    "casting", cast->casting,
                    "requires_pyapi", cast->flags & NPY_METH_REQUIRES_PYAPI,
                    "supports_unaligned",
                        cast->flags & NPY_METH_SUPPORTS_UNALIGNED,
                    "no_floatingpoint_errors",
                        cast->flags & NPY_METH_NO_FLOATINGPOINT_ERRORS,
                    "name", cast->name);
            if (cast_info == NULL) {
                goto fail;
            }
            int res = PyList_Append(result, cast_info);
            Py_DECREF(cast_info);
            if (res < 0) {
                goto fail;
            }
        }
    }
    Py_DECREF(classes);
    return result;

  fail:
    Py_XDECREF(classes);
    Py_XDECREF(result);
    return NULL;
}


/*
 * Helper to test the identity cache, takes a list of values and adds
 * all to the cache except the last key/value pair.  The last value is
 * ignored, instead the last key is looked up.
 * None is returned, if the key is not found.
 * If `replace` is True, duplicate entries are ignored when adding to the
 * hashtable.
 */
static PyObject *
identityhash_tester(PyObject *NPY_UNUSED(mod),
        PyObject *const *args, Py_ssize_t len_args, PyObject *kwnames)
{
    NPY_PREPARE_ARGPARSER;

    int key_len;
    int replace;
    PyObject *replace_obj = Py_False;
    PyObject *sequence;
    PyObject *result = NULL;

    if (npy_parse_arguments("identityhash_tester", args, len_args, kwnames,
            "key_len", &PyArray_PythonPyIntFromInt, &key_len,
            "sequence", NULL, &sequence,
            "|replace", NULL, &replace_obj,
            NULL, NULL, NULL) < 0) {
        return NULL;
    }
    replace = PyObject_IsTrue(replace_obj);
    if (error_converting(replace)) {
        return NULL;
    }

    if (key_len < 1 || key_len >= NPY_MAXARGS) {
        PyErr_SetString(PyExc_ValueError, "must have 1 to max-args keys.");
        return NULL;
    }
    PyArrayIdentityHash *tb = PyArrayIdentityHash_New(key_len);
    if (tb == NULL) {
        return NULL;
    }

    /* Replace the sequence with a guaranteed fast-sequence */
    sequence = PySequence_Fast(sequence, "converting sequence.");
    if (sequence == NULL) {
        goto finish;
    }

    Py_ssize_t length = PySequence_Fast_GET_SIZE(sequence);
    for (Py_ssize_t i = 0; i < length; i++) {
        PyObject *key_val = PySequence_Fast_GET_ITEM(sequence, i);
        if (!PyTuple_CheckExact(key_val) || PyTuple_GET_SIZE(key_val) != 2) {
            PyErr_SetString(PyExc_TypeError, "bad key-value pair.");
            goto finish;
        }
        PyObject *key = PyTuple_GET_ITEM(key_val, 0);
        PyObject *value = PyTuple_GET_ITEM(key_val, 1);
        if (!PyTuple_CheckExact(key) || PyTuple_GET_SIZE(key) != key_len) {
            PyErr_SetString(PyExc_TypeError, "bad key tuple.");
            goto finish;
        }

        PyObject *keys[NPY_MAXARGS];
        for (int j = 0; j < key_len; j++) {
            keys[j] = PyTuple_GET_ITEM(key, j);
        }
        if (i != length - 1) {
            if (PyArrayIdentityHash_SetItem(tb, keys, value, replace) < 0) {
                goto finish;
            }
        }
        else {
            result = PyArrayIdentityHash_GetItem(tb, keys);
            if (result == NULL) {
                result = Py_None;
            }
            Py_INCREF(result);
        }
    }

  finish:
    Py_DECREF(sequence);
    PyArrayIdentityHash_Dealloc(tb);
    return result;
}


/*
 * Test C-api level item getting.
 */
static PyObject *
array_indexing(PyObject *NPY_UNUSED(self), PyObject *args)
{
    int mode;
    Py_ssize_t i;
    PyObject *arr, *op = NULL;

    if (!PyArg_ParseTuple(args, "iOn|O", &mode, &arr, &i, &op)) {
        return NULL;
    }

    if (mode == 0) {
        return PySequence_GetItem(arr, i);
    }
    if (mode == 1) {
        if (PySequence_SetItem(arr, i, op) < 0) {
            return NULL;
        }
        Py_RETURN_NONE;
    }

    PyErr_SetString(PyExc_ValueError,
                    "invalid mode. 0: item 1: assign");
    return NULL;
}

/*
 * Test C-api PyArray_AsCArray item getter
 */
static PyObject *
test_as_c_array(PyObject *NPY_UNUSED(self), PyObject *args)
{
    PyArrayObject *array_obj;
    npy_intp dims[3];   /* max 3-dim */
    npy_intp i=0, j=0, k=0;
    npy_intp num_dims = 0;
    PyArray_Descr *descr = NULL;
    double *array1 = NULL;
    double **array2 = NULL;
    double ***array3 = NULL;
    double temp = 9999;

    if (!PyArg_ParseTuple(args, "O!l|ll",
                &PyArray_Type, &array_obj,
                &i, &j, &k)) {
        return NULL;
    }

    if (NULL == array_obj) {
        return NULL;
    }

    num_dims = PyArray_NDIM(array_obj);
    descr = PyArray_DESCR(array_obj);
    Py_INCREF(descr);  /* PyArray_AsCArray steals a reference to this */

    switch (num_dims) {
        case 1:
            if (PyArray_AsCArray(
                    (PyObject **) &array_obj,
                    (void *) &array1,
                    dims,
                    1,
                    descr) < 0) {
                PyErr_SetString(PyExc_RuntimeError, "error converting 1D array");
                return NULL;
            }
            temp = array1[i];
            PyArray_Free((PyObject *) array_obj, (void *) array1);
            break;
        case 2:
            if (PyArray_AsCArray(
                    (PyObject **) &array_obj,
                    (void **) &array2,
                    dims,
                    2,
                    descr) < 0) {
                PyErr_SetString(PyExc_RuntimeError, "error converting 2D array");
                return NULL;
            }
            temp = array2[i][j];
            PyArray_Free((PyObject *) array_obj, (void *) array2);
            break;
        case 3:
            if (PyArray_AsCArray(
                    (PyObject **) &array_obj,
                    (void ***) &array3,
                    dims,
                    3,
                    descr) < 0) {
                PyErr_SetString(PyExc_RuntimeError, "error converting 3D array");
                return NULL;
            }
            temp = array3[i][j][k];
            PyArray_Free((PyObject *) array_obj, (void *) array3);
            break;
        default:
            Py_DECREF(descr);
            PyErr_SetString(PyExc_ValueError, "array.ndim not in [1, 3]");
            return NULL;
    }
    return Py_BuildValue("f", temp);
}

/*
 * Test nditer of too large arrays using remove axis, etc.
 */
static PyObject *
test_nditer_too_large(PyObject *NPY_UNUSED(self), PyObject *args) {
    NpyIter *iter;
    PyObject *array_tuple, *arr;
    PyArrayObject *arrays[NPY_MAXARGS];
    npy_uint32 op_flags[NPY_MAXARGS];
    Py_ssize_t nop;
    int i, axis, mode;

    npy_intp index[NPY_MAXARGS] = {0};
    char *msg;

    if (!PyArg_ParseTuple(args, "Oii", &array_tuple, &axis, &mode)) {
        return NULL;
    }

    if (!PyTuple_CheckExact(array_tuple)) {
        PyErr_SetString(PyExc_ValueError, "tuple required as first argument");
        return NULL;
    }
    nop = PyTuple_Size(array_tuple);
    if (nop > NPY_MAXARGS) {
        PyErr_SetString(PyExc_ValueError, "tuple must be smaller then maxargs");
        return NULL;
    }

    for (i=0; i < nop; i++) {
        arr = PyTuple_GET_ITEM(array_tuple, i);
        if (!PyArray_CheckExact(arr)) {
            PyErr_SetString(PyExc_ValueError, "require base class ndarray");
            return NULL;
        }
        arrays[i] = (PyArrayObject *)arr;
        op_flags[i] = NPY_ITER_READONLY;
    }

    iter = NpyIter_MultiNew(nop, arrays, NPY_ITER_MULTI_INDEX | NPY_ITER_RANGED,
                            NPY_KEEPORDER, NPY_NO_CASTING, op_flags, NULL);

    if (iter == NULL) {
        return NULL;
    }

    /* Remove an axis (negative, do not remove any) */
    if (axis >= 0) {
        if (!NpyIter_RemoveAxis(iter, axis)) {
            goto fail;
        }
    }

    switch (mode) {
        /* Test IterNext getting */
        case 0:
            if (NpyIter_GetIterNext(iter, NULL) == NULL) {
                goto fail;
            }
            break;
        case 1:
            if (NpyIter_GetIterNext(iter, &msg) == NULL) {
                PyErr_SetString(PyExc_ValueError, msg);
                goto fail;
            }
            break;
        /* Test Multi Index removal */
        case 2:
            if (!NpyIter_RemoveMultiIndex(iter)) {
                goto fail;
            }
            break;
        /* Test GotoMultiIndex (just 0 hardcoded) */
        case 3:
            if (!NpyIter_GotoMultiIndex(iter, index)) {
                goto fail;
            }
            break;
        /* Test setting iterrange (hardcoded range of 0, 1) */
        case 4:
            if (!NpyIter_ResetToIterIndexRange(iter, 0, 1, NULL)) {
                goto fail;
            }
            break;
        case 5:
            if (!NpyIter_ResetToIterIndexRange(iter, 0, 1, &msg)) {
                PyErr_SetString(PyExc_ValueError, msg);
                goto fail;
            }
            break;
        /* Do nothing */
        default:
            break;
    }

    NpyIter_Deallocate(iter);
    Py_RETURN_NONE;
  fail:
    NpyIter_Deallocate(iter);
    return NULL;
}

static PyObject *
array_solve_diophantine(PyObject *NPY_UNUSED(ignored), PyObject *args, PyObject *kwds)
{
    PyObject *A = NULL;
    PyObject *U = NULL;
    Py_ssize_t b_input = 0;
    Py_ssize_t max_work = -1;
    int simplify = 0;
    int require_ub_nontrivial = 0;
    static char *kwlist[] = {"A", "U", "b", "max_work", "simplify",
                             "require_ub_nontrivial", NULL};

    diophantine_term_t terms[2*NPY_MAXDIMS+2];
    npy_int64 x[2*NPY_MAXDIMS+2];
    npy_int64 b;
    unsigned int nterms, j;
    mem_overlap_t result = MEM_OVERLAP_YES;
    PyObject *retval = NULL;
    NPY_BEGIN_THREADS_DEF;

    if (!PyArg_ParseTupleAndKeywords(args, kwds, "O!O!n|nii", kwlist,
                                     &PyTuple_Type, &A,
                                     &PyTuple_Type, &U,
                                     &b_input, &max_work, &simplify,
                                     &require_ub_nontrivial)) {
        return NULL;
    }

    if (PyTuple_GET_SIZE(A) > (Py_ssize_t)ARRAY_SIZE(terms)) {
        PyErr_SetString(PyExc_ValueError, "too many terms in equation");
        goto fail;
    }

    nterms = PyTuple_GET_SIZE(A);

    if (PyTuple_GET_SIZE(U) != nterms) {
        PyErr_SetString(PyExc_ValueError, "A, U must be tuples of equal length");
        goto fail;
    }

    for (j = 0; j < nterms; ++j) {
        terms[j].a = (npy_int64)PyLong_AsSsize_t(PyTuple_GET_ITEM(A, j));
        if (error_converting(terms[j].a)) {
            goto fail;
        }
        terms[j].ub = (npy_int64)PyLong_AsSsize_t(PyTuple_GET_ITEM(U, j));
        if (error_converting(terms[j].ub)) {
            goto fail;
        }
    }

    b = b_input;

    NPY_BEGIN_THREADS;
    if (simplify && !require_ub_nontrivial) {
        if (diophantine_simplify(&nterms, terms, b)) {
            result = MEM_OVERLAP_OVERFLOW;
        }
    }
    if (result == MEM_OVERLAP_YES) {
        result = solve_diophantine(nterms, terms, b, max_work, require_ub_nontrivial, x);
    }
    NPY_END_THREADS;

    if (result == MEM_OVERLAP_YES) {
        retval = PyTuple_New(nterms);
        if (retval == NULL) {
            goto fail;
        }

        for (j = 0; j < nterms; ++j) {
            PyObject *obj;
            obj = PyLong_FromSsize_t(x[j]);
            if (obj == NULL) {
                goto fail;
            }
            PyTuple_SET_ITEM(retval, j, obj);
        }
    }
    else if (result == MEM_OVERLAP_NO) {
        retval = Py_None;
        Py_INCREF(retval);
    }
    else if (result == MEM_OVERLAP_ERROR) {
        PyErr_SetString(PyExc_ValueError, "Invalid arguments");
    }
    else if (result == MEM_OVERLAP_OVERFLOW) {
        PyErr_SetString(PyExc_OverflowError, "Integer overflow");
    }
    else if (result == MEM_OVERLAP_TOO_HARD) {
        PyErr_SetString(PyExc_RuntimeError, "Too much work done");
    }
    else {
        PyErr_SetString(PyExc_RuntimeError, "Unknown error");
    }

    return retval;

fail:
    Py_XDECREF(retval);
    return NULL;
}


static PyObject *
array_internal_overlap(PyObject *NPY_UNUSED(ignored), PyObject *args, PyObject *kwds)
{
    PyArrayObject * self = NULL;
    static char *kwlist[] = {"self", "max_work", NULL};

    mem_overlap_t result;
    Py_ssize_t max_work = NPY_MAY_SHARE_EXACT;
    NPY_BEGIN_THREADS_DEF;

    if (!PyArg_ParseTupleAndKeywords(args, kwds, "O&|n", kwlist,
                                     PyArray_Converter, &self,
                                     &max_work)) {
        return NULL;
    }

    if (max_work < -2) {
        PyErr_SetString(PyExc_ValueError, "Invalid value for max_work");
        goto fail;
    }

    NPY_BEGIN_THREADS;
    result = solve_may_have_internal_overlap(self, max_work);
    NPY_END_THREADS;

    Py_XDECREF(self);

    if (result == MEM_OVERLAP_NO) {
        Py_RETURN_FALSE;
    }
    else if (result == MEM_OVERLAP_YES) {
        Py_RETURN_TRUE;
    }
    else if (result == MEM_OVERLAP_OVERFLOW) {
        PyErr_SetString(PyExc_OverflowError,
                        "Integer overflow in computing overlap");
        return NULL;
    }
    else if (result == MEM_OVERLAP_TOO_HARD) {
        PyErr_SetString(PyExc_ValueError,
                        "Exceeded max_work");
        return NULL;
    }
    else {
        /* Doesn't happen usually */
        PyErr_SetString(PyExc_RuntimeError,
                        "Error in computing overlap");
        return NULL;
    }

fail:
    Py_XDECREF(self);
    return NULL;
}


static PyObject *
pylong_from_int128(npy_extint128_t value)
{
    PyObject *val_64 = NULL, *val = NULL, *tmp = NULL, *tmp2 = NULL;

    val_64 = PyLong_FromLong(64);
    if (val_64 == NULL) {
        goto fail;
    }

    val = PyLong_FromUnsignedLongLong(value.hi);
    if (val == NULL) {
        goto fail;
    }

    tmp = PyNumber_Lshift(val, val_64);
    if (tmp == NULL) {
        goto fail;
    }

    Py_DECREF(val);
    Py_DECREF(val_64);
    val = tmp;
    val_64 = NULL;

    tmp = PyLong_FromUnsignedLongLong(value.lo);
    if (tmp == NULL) {
        goto fail;
    }

    tmp2 = PyNumber_Or(val, tmp);
    if (tmp2 == NULL) {
        goto fail;
    }

    Py_DECREF(val);
    Py_DECREF(tmp);

    val = NULL;
    tmp = NULL;

    if (value.sign < 0) {
        val = PyNumber_Negative(tmp2);
        if (val == NULL) {
            goto fail;
        }
        Py_DECREF(tmp2);
        return val;
    }
    else {
        val = tmp2;
    }
    return val;

fail:
    Py_XDECREF(val_64);
    Py_XDECREF(tmp);
    Py_XDECREF(tmp2);
    Py_XDECREF(val);
    return NULL;
}


static int
int128_from_pylong(PyObject *obj, npy_extint128_t *result)
{
    PyObject *long_obj = NULL, *val_64 = NULL, *val_0 = NULL,
        *mask_64 = NULL, *max_128 = NULL, *hi_bits = NULL,
        *lo_bits = NULL, *tmp = NULL;
    int cmp;
    int negative_zero = 0;

    if (PyBool_Check(obj)) {
        /* False means negative zero */
        negative_zero = 1;
    }

    long_obj = PyObject_CallFunction((PyObject*)&PyLong_Type, "O", obj);
    if (long_obj == NULL) {
        goto fail;
    }

    val_0 = PyLong_FromLong(0);
    if (val_0 == NULL) {
        goto fail;
    }

    val_64 = PyLong_FromLong(64);
    if (val_64 == NULL) {
        goto fail;
    }

    mask_64 = PyLong_FromUnsignedLongLong(0xffffffffffffffffULL);
    if (mask_64 == NULL) {
        goto fail;
    }

    tmp = PyNumber_Lshift(mask_64, val_64);
    if (tmp == NULL) {
        goto fail;
    }
    max_128 = PyNumber_Or(tmp, mask_64);
    if (max_128 == NULL) {
        goto fail;
    }
    Py_DECREF(tmp);
    tmp = NULL;

    cmp = PyObject_RichCompareBool(long_obj, val_0, Py_LT);
    if (cmp == -1) {
        goto fail;
    }
    else if (cmp == 1) {
        tmp = PyNumber_Negative(long_obj);
        if (tmp == NULL) {
            goto fail;
        }
        Py_DECREF(long_obj);
        long_obj = tmp;
        tmp = NULL;
        result->sign = -1;
    }
    else {
        result->sign = 1;
    }

    cmp = PyObject_RichCompareBool(long_obj, max_128, Py_GT);
    if (cmp == 1) {
        PyErr_SetString(PyExc_OverflowError, "");
        goto fail;
    }
    else if (cmp == -1) {
        goto fail;
    }

    hi_bits = PyNumber_Rshift(long_obj, val_64);
    if (hi_bits == NULL) {
        goto fail;
    }

    lo_bits = PyNumber_And(long_obj, mask_64);
    if (lo_bits == NULL) {
        goto fail;
    }

    result->hi = PyLong_AsUnsignedLongLong(hi_bits);
    if (result->hi == (unsigned PY_LONG_LONG)-1 && PyErr_Occurred()) {
        goto fail;
    }

    result->lo = PyLong_AsUnsignedLongLong(lo_bits);
    if (result->lo == (unsigned PY_LONG_LONG)-1 && PyErr_Occurred()) {
        goto fail;
    }

    if (negative_zero && result->hi == 0 && result->lo == 0) {
        result->sign = -1;
    }

    Py_XDECREF(long_obj);
    Py_XDECREF(val_64);
    Py_XDECREF(val_0);
    Py_XDECREF(mask_64);
    Py_XDECREF(max_128);
    Py_XDECREF(hi_bits);
    Py_XDECREF(lo_bits);
    Py_XDECREF(tmp);
    return 0;

fail:
    Py_XDECREF(long_obj);
    Py_XDECREF(val_64);
    Py_XDECREF(val_0);
    Py_XDECREF(mask_64);
    Py_XDECREF(max_128);
    Py_XDECREF(hi_bits);
    Py_XDECREF(lo_bits);
    Py_XDECREF(tmp);
    return -1;
}


static PyObject *
extint_safe_binop(PyObject *NPY_UNUSED(self), PyObject *args) {
    PY_LONG_LONG a, b, c;
    int op;
    char overflow = 0;
    if (!PyArg_ParseTuple(args, "LLi", &a, &b, &op)) {
        return NULL;
    }
    if (op == 1) {
        c = safe_add(a, b, &overflow);
    }
    else if (op == 2) {
        c = safe_sub(a, b, &overflow);
    }
    else if (op == 3) {
        c = safe_mul(a, b, &overflow);
    }
    else {
        PyErr_SetString(PyExc_ValueError, "invalid op");
        return NULL;
    }
    if (overflow) {
        PyErr_SetString(PyExc_OverflowError, "");
        return NULL;
    }
    return PyLong_FromLongLong(c);
}


static PyObject *
extint_to_128(PyObject *NPY_UNUSED(self), PyObject *args) {
    PY_LONG_LONG a;
    if (!PyArg_ParseTuple(args, "L", &a)) {
        return NULL;
    }
    return pylong_from_int128(to_128(a));
}


static PyObject *
extint_to_64(PyObject *NPY_UNUSED(self), PyObject *args) {
    PyObject *a_obj;
    npy_extint128_t a;
    PY_LONG_LONG r;
    char overflow = 0;
    if (!PyArg_ParseTuple(args, "O", &a_obj)) {
        return NULL;
    }
    if (int128_from_pylong(a_obj, &a)) {
        return NULL;
    }
    r = to_64(a, &overflow);
    if (overflow) {
        PyErr_SetString(PyExc_OverflowError, "");
        return NULL;
    }
    return PyLong_FromLongLong(r);
}


static PyObject *
extint_mul_64_64(PyObject *NPY_UNUSED(self), PyObject *args) {
    PY_LONG_LONG a, b;
    npy_extint128_t c;
    if (!PyArg_ParseTuple(args, "LL", &a, &b)) {
        return NULL;
    }
    c = mul_64_64(a, b);
    return pylong_from_int128(c);
}


static PyObject *
extint_add_128(PyObject *NPY_UNUSED(self), PyObject *args) {
    PyObject *a_obj, *b_obj;
    npy_extint128_t a, b, c;
    char overflow = 0;
    if (!PyArg_ParseTuple(args, "OO", &a_obj, &b_obj)) {
        return NULL;
    }
    if (int128_from_pylong(a_obj, &a) || int128_from_pylong(b_obj, &b)) {
        return NULL;
    }
    c = add_128(a, b, &overflow);
    if (overflow) {
        PyErr_SetString(PyExc_OverflowError, "");
        return NULL;
    }
    return pylong_from_int128(c);
}


static PyObject *
extint_sub_128(PyObject *NPY_UNUSED(self), PyObject *args) {
    PyObject *a_obj, *b_obj;
    npy_extint128_t a, b, c;
    char overflow = 0;
    if (!PyArg_ParseTuple(args, "OO", &a_obj, &b_obj)) {
        return NULL;
    }
    if (int128_from_pylong(a_obj, &a) || int128_from_pylong(b_obj, &b)) {
        return NULL;
    }
    c = sub_128(a, b, &overflow);
    if (overflow) {
        PyErr_SetString(PyExc_OverflowError, "");
        return NULL;
    }
    return pylong_from_int128(c);
}


static PyObject *
extint_neg_128(PyObject *NPY_UNUSED(self), PyObject *args) {
    PyObject *a_obj;
    npy_extint128_t a, b;
    if (!PyArg_ParseTuple(args, "O", &a_obj)) {
        return NULL;
    }
    if (int128_from_pylong(a_obj, &a)) {
        return NULL;
    }
    b = neg_128(a);
    return pylong_from_int128(b);
}


static PyObject *
extint_shl_128(PyObject *NPY_UNUSED(self), PyObject *args) {
    PyObject *a_obj;
    npy_extint128_t a, b;
    if (!PyArg_ParseTuple(args, "O", &a_obj)) {
        return NULL;
    }
    if (int128_from_pylong(a_obj, &a)) {
        return NULL;
    }
    b = shl_128(a);
    return pylong_from_int128(b);
}


static PyObject *
extint_shr_128(PyObject *NPY_UNUSED(self), PyObject *args) {
    PyObject *a_obj;
    npy_extint128_t a, b;
    if (!PyArg_ParseTuple(args, "O", &a_obj)) {
        return NULL;
    }
    if (int128_from_pylong(a_obj, &a)) {
        return NULL;
    }
    b = shr_128(a);
    return pylong_from_int128(b);
}


static PyObject *
extint_gt_128(PyObject *NPY_UNUSED(self), PyObject *args) {
    PyObject *a_obj, *b_obj;
    npy_extint128_t a, b;
    if (!PyArg_ParseTuple(args, "OO", &a_obj, &b_obj)) {
        return NULL;
    }
    if (int128_from_pylong(a_obj, &a) || int128_from_pylong(b_obj, &b)) {
        return NULL;
    }
    if (gt_128(a, b)) {
        Py_RETURN_TRUE;
    }
    else {
        Py_RETURN_FALSE;
    }
}


static PyObject *
extint_divmod_128_64(PyObject *NPY_UNUSED(self), PyObject *args) {
    PyObject *a_obj, *ret = NULL, *tmp = NULL;
    npy_extint128_t a, c;
    PY_LONG_LONG b;
    npy_int64 mod;
    if (!PyArg_ParseTuple(args, "OL", &a_obj, &b)) {
        goto fail;
    }
    if (b <= 0) {
        PyErr_SetString(PyExc_ValueError, "");
        goto fail;
    }
    if (int128_from_pylong(a_obj, &a)) {
        goto fail;
    }

    c = divmod_128_64(a, b, &mod);

    ret = PyTuple_New(2);

    tmp = pylong_from_int128(c);
    if (tmp == NULL) {
        goto fail;
    }
    PyTuple_SET_ITEM(ret, 0, tmp);

    tmp = PyLong_FromLongLong(mod);
    if (tmp == NULL) {
        goto fail;
    }
    PyTuple_SET_ITEM(ret, 1, tmp);
    return ret;

fail:
    Py_XDECREF(ret);
    Py_XDECREF(tmp);
    return NULL;
}


static PyObject *
extint_floordiv_128_64(PyObject *NPY_UNUSED(self), PyObject *args) {
    PyObject *a_obj;
    npy_extint128_t a, c;
    PY_LONG_LONG b;
    if (!PyArg_ParseTuple(args, "OL", &a_obj, &b)) {
        return NULL;
    }
    if (b <= 0) {
        PyErr_SetString(PyExc_ValueError, "");
        return NULL;
    }
    if (int128_from_pylong(a_obj, &a)) {
        return NULL;
    }
    c = floordiv_128_64(a, b);
    return pylong_from_int128(c);
}


static PyObject *
extint_ceildiv_128_64(PyObject *NPY_UNUSED(self), PyObject *args) {
    PyObject *a_obj;
    npy_extint128_t a, c;
    PY_LONG_LONG b;
    if (!PyArg_ParseTuple(args, "OL", &a_obj, &b)) {
        return NULL;
    }
    if (b <= 0) {
        PyErr_SetString(PyExc_ValueError, "");
        return NULL;
    }
    if (int128_from_pylong(a_obj, &a)) {
        return NULL;
    }
    c = ceildiv_128_64(a, b);
    return pylong_from_int128(c);
}

struct TestStruct1 {
    npy_uint8 a;
    npy_complex64 b;
};

struct TestStruct2 {
    npy_uint32 a;
    npy_complex64 b;
};

struct TestStruct3 {
    npy_uint8 a;
    struct TestStruct1 b;
};

static PyObject *
get_struct_alignments(PyObject *NPY_UNUSED(self), PyObject *args) {
    PyObject *ret = PyTuple_New(3);
    PyObject *alignment, *size, *val;

    if (ret == NULL) {
        return NULL;
    }

/**begin repeat
 * #N = 1,2,3#
 */
    alignment = PyLong_FromLong(NPY_ALIGNOF(struct TestStruct@N@));
    size = PyLong_FromLong(sizeof(struct TestStruct@N@));
    val = PyTuple_Pack(2, alignment, size);
    Py_DECREF(alignment);
    Py_DECREF(size);
    if (val == NULL) {
        Py_DECREF(ret);
        return NULL;
    }
    PyTuple_SET_ITEM(ret, @N@-1, val);
/**end repeat**/
    return ret;
}


static char get_fpu_mode_doc[] = (
    "get_fpu_mode()\n"
    "\n"
    "Get the current FPU control word, in a platform-dependent format.\n"
    "Returns None if not implemented on current platform.");

static PyObject *
get_fpu_mode(PyObject *NPY_UNUSED(self), PyObject *args)
{
    if (!PyArg_ParseTuple(args, "")) {
        return NULL;
    }

#if defined(_MSC_VER) && !defined(__clang__)
    {
        unsigned int result = 0;
        result = _controlfp(0, 0);
        return PyLong_FromLongLong(result);
    }
#elif (defined(__GNUC__) && (defined(__x86_64__) || defined(__i386__))) \
  || (defined(_MSC_VER) && defined(__clang__) && \
      (defined(_M_IX86) || defined(_M_AMD64)))
    {
        unsigned short cw = 0;
        __asm__("fstcw %w0" : "=m" (cw));
        return PyLong_FromLongLong(cw);
    }
#else
    Py_RETURN_NONE;
#endif
}

/*
 * npymath wrappers
 */

/**begin repeat
 * #name = cabs, carg#
 */

/**begin repeat1
 * #itype = npy_cfloat, npy_cdouble, npy_clongdouble#
 * #ITYPE = NPY_CFLOAT, NPY_CDOUBLE, NPY_CLONGDOUBLE#
 * #otype = npy_float, npy_double, npy_longdouble#
 * #OTYPE = NPY_FLOAT, NPY_DOUBLE, NPY_LONGDOUBLE#
 * #suffix= f, , l#
 */

static PyObject *
call_npy_@name@@suffix@(PyObject *NPY_UNUSED(self), PyObject *args)
{
    PyObject *z_py = NULL, *z_arr = NULL, *w_arr = NULL;

    if (!PyArg_ParseTuple(args, "O", &z_py)) {
        return NULL;
    }

    z_arr = PyArray_FROMANY(z_py, @ITYPE@, 0, 0, NPY_ARRAY_CARRAY_RO);
    if (z_arr == NULL) {
        return NULL;
    }

    w_arr = PyArray_SimpleNew(0, NULL, @OTYPE@);
    if (w_arr == NULL) {
        Py_DECREF(z_arr);
        return NULL;
    }

    *(@otype@*)PyArray_DATA((PyArrayObject *)w_arr) =
        npy_@name@@suffix@(*(@itype@*)PyArray_DATA((PyArrayObject *)z_arr));

    Py_DECREF(z_arr);
    return w_arr;
}

/**end repeat1**/

/**end repeat**/

/**begin repeat
 * #name = log10, cosh, sinh, tan, tanh#
 */

/**begin repeat1
 * #type = npy_float, npy_double, npy_longdouble#
 * #TYPE = NPY_FLOAT, NPY_DOUBLE, NPY_LONGDOUBLE#
 * #suffix= f, , l#
 */

static PyObject *
call_npy_@name@@suffix@(PyObject *NPY_UNUSED(self), PyObject *args)
{
    PyObject *z_py = NULL, *z_arr = NULL, *w_arr = NULL;

    if (!PyArg_ParseTuple(args, "O", &z_py)) {
        return NULL;
    }

    z_arr = PyArray_FROMANY(z_py, @TYPE@, 0, 0, NPY_ARRAY_CARRAY_RO);
    if (z_arr == NULL) {
        return NULL;
    }

    w_arr = PyArray_SimpleNew(0, NULL, @TYPE@);
    if (w_arr == NULL) {
        Py_DECREF(z_arr);
        return NULL;
    }

    *(@type@*)PyArray_DATA((PyArrayObject *)w_arr) =
        npy_@name@@suffix@(*(@type@*)PyArray_DATA((PyArrayObject *)z_arr));

    Py_DECREF(z_arr);
    return w_arr;
}

/**end repeat1**/

/**end repeat**/

/*
 * For development/testing purposes, it's convenient to have access to the
 * system printf for floats. This is a very simple printf interface.
 */
PyObject *
PrintFloat_Printf_g(PyObject *obj, int precision)
{
    char str[1024];

    if (PyArray_IsScalar(obj, Half)) {
        npy_half x = PyArrayScalar_VAL(obj, Half);
        PyOS_snprintf(str, sizeof(str), "%.*g", precision,
                      npy_half_to_double(x));
    }
    else if (PyArray_IsScalar(obj, Float)) {
        npy_float x = PyArrayScalar_VAL(obj, Float);
        PyOS_snprintf(str, sizeof(str), "%.*g", precision, x);
    }
    else if (PyArray_IsScalar(obj, Double)) {
        npy_double x = PyArrayScalar_VAL(obj, Double);
        PyOS_snprintf(str, sizeof(str), "%.*g", precision, x);
        /* would be better to use lg, but not available in C90 */
    }
    else if (PyArray_IsScalar(obj, LongDouble)) {
        npy_longdouble x = PyArrayScalar_VAL(obj, LongDouble);
        PyOS_snprintf(str, sizeof(str), "%.*" NPY_LONGDOUBLE_FMT, precision, x);
    }
    else{
        double val = PyFloat_AsDouble(obj);
        if (error_converting(val)) {
            return NULL;
        }
        PyOS_snprintf(str, sizeof(str), "%.*g", precision, val);
    }

    return PyUnicode_FromString(str);
}

/*
 *  format_float_OSprintf_g(val, precision)
 *
 *  Print a floating point scalar using the system's printf function,
 *  equivalent to:
 *
 *      printf("%.*g", precision, val);
 *
 *  for half/float/double, or replacing 'g' by 'Lg' for longdouble. This
 *  method is designed to help cross-validate the format_float_* methods.
 *
 *  Parameters
 *  ----------
 *  val : python float or numpy floating scalar
 *      Value to format.
 *
 *  precision : non-negative integer, optional
 *      Precision given to printf.
 *
 *  Returns
 *  -------
 *  rep : string
 *      The string representation of the floating point value
 *
 *  See Also
 *  --------
 *  format_float_scientific
 *  format_float_positional
 */
static PyObject *
printf_float_g(PyObject *NPY_UNUSED(dummy), PyObject *args, PyObject *kwds)
{
    PyObject *obj;
    int precision;

    if (!PyArg_ParseTuple(args,"Oi:format_float_OSprintf_g", &obj,
                                                             &precision)) {
        return NULL;
    }

    if (precision < 0) {
        PyErr_SetString(PyExc_TypeError, "precision must be non-negative");
        return NULL;
    }

    return PrintFloat_Printf_g(obj, precision);
}

static PyObject *
run_byteorder_converter(PyObject* NPY_UNUSED(self), PyObject *args)
{
    char byteorder;
    if (!PyArg_ParseTuple(args, "O&", PyArray_ByteorderConverter, &byteorder)) {
        return NULL;
    }
    switch (byteorder) {
        case NPY_BIG: return PyUnicode_FromString("NPY_BIG");
        case NPY_LITTLE: return PyUnicode_FromString("NPY_LITTLE");
        case NPY_NATIVE: return PyUnicode_FromString("NPY_NATIVE");
        case NPY_SWAP: return PyUnicode_FromString("NPY_SWAP");
        case NPY_IGNORE: return PyUnicode_FromString("NPY_IGNORE");
    }
    return PyLong_FromLong(byteorder);
}

static PyObject *
run_sortkind_converter(PyObject* NPY_UNUSED(self), PyObject *args)
{
    NPY_SORTKIND kind;
    if (!PyArg_ParseTuple(args, "O&", PyArray_SortkindConverter, &kind)) {
        return NULL;
    }
    switch (kind) {
        case _NPY_SORT_UNDEFINED: return PyUnicode_FromString("_NPY_SORT_UNDEFINED");
        case NPY_QUICKSORT: return PyUnicode_FromString("NPY_QUICKSORT");
        case NPY_HEAPSORT: return PyUnicode_FromString("NPY_HEAPSORT");
        case NPY_STABLESORT: return PyUnicode_FromString("NPY_STABLESORT");
    }
    return PyLong_FromLong(kind);
}

static PyObject *
run_selectkind_converter(PyObject* NPY_UNUSED(self), PyObject *args)
{
    NPY_SELECTKIND kind;
    if (!PyArg_ParseTuple(args, "O&", PyArray_SelectkindConverter, &kind)) {
        return NULL;
    }
    switch (kind) {
        case NPY_INTROSELECT: return PyUnicode_FromString("NPY_INTROSELECT");
    }
    return PyLong_FromLong(kind);
}

static PyObject *
run_searchside_converter(PyObject* NPY_UNUSED(self), PyObject *args)
{
    NPY_SEARCHSIDE side;
    if (!PyArg_ParseTuple(args, "O&", PyArray_SearchsideConverter, &side)) {
        return NULL;
    }
    switch (side) {
        case NPY_SEARCHLEFT: return PyUnicode_FromString("NPY_SEARCHLEFT");
        case NPY_SEARCHRIGHT: return PyUnicode_FromString("NPY_SEARCHRIGHT");
    }
    return PyLong_FromLong(side);
}

static PyObject *
run_order_converter(PyObject* NPY_UNUSED(self), PyObject *args)
{
    NPY_ORDER order;
    if (!PyArg_ParseTuple(args, "O&", PyArray_OrderConverter, &order)) {
        return NULL;
    }
    switch (order) {
        case NPY_ANYORDER: return PyUnicode_FromString("NPY_ANYORDER");
        case NPY_CORDER: return PyUnicode_FromString("NPY_CORDER");
        case NPY_FORTRANORDER: return PyUnicode_FromString("NPY_FORTRANORDER");
        case NPY_KEEPORDER: return PyUnicode_FromString("NPY_KEEPORDER");
    }
    return PyLong_FromLong(order);
}

static PyObject *
run_clipmode_converter(PyObject* NPY_UNUSED(self), PyObject *args)
{
    NPY_CLIPMODE mode;
    if (!PyArg_ParseTuple(args, "O&", PyArray_ClipmodeConverter, &mode)) {
        return NULL;
    }
    switch (mode) {
        case NPY_CLIP: return PyUnicode_FromString("NPY_CLIP");
        case NPY_WRAP: return PyUnicode_FromString("NPY_WRAP");
        case NPY_RAISE: return PyUnicode_FromString("NPY_RAISE");
    }
    return PyLong_FromLong(mode);
}

static PyObject *
run_casting_converter(PyObject* NPY_UNUSED(self), PyObject *args)
{
    NPY_CASTING casting;
    if (!PyArg_ParseTuple(args, "O&", PyArray_CastingConverter, &casting)) {
        return NULL;
    }
    switch (casting) {
        case NPY_NO_CASTING: return PyUnicode_FromString("NPY_NO_CASTING");
        case NPY_EQUIV_CASTING: return PyUnicode_FromString("NPY_EQUIV_CASTING");
        case NPY_SAFE_CASTING: return PyUnicode_FromString("NPY_SAFE_CASTING");
        case NPY_SAME_KIND_CASTING: return PyUnicode_FromString("NPY_SAME_KIND_CASTING");
        case NPY_UNSAFE_CASTING: return PyUnicode_FromString("NPY_UNSAFE_CASTING");
        default: return PyLong_FromLong(casting);
    }
}

static PyObject *
run_intp_converter(PyObject* NPY_UNUSED(self), PyObject *args)
{
    PyArray_Dims dims = {NULL, -1};
    if (!PyArg_ParseTuple(args, "O&", PyArray_IntpConverter, &dims)) {
        return NULL;
    }
    if (dims.len == -1) {
        Py_RETURN_NONE;
    }

    PyObject *tup = PyArray_IntTupleFromIntp(dims.len, dims.ptr);
    PyDimMem_FREE(dims.ptr);
    return tup;
}

/* used to test NPY_ARRAY_ENSURENOCOPY raises ValueError */
static PyObject*
npy_ensurenocopy(PyObject* NPY_UNUSED(self), PyObject* args)
{
    int flags = NPY_ARRAY_ENSURENOCOPY;
    if (!PyArray_CheckFromAny(args, NULL, 0, 0, flags, NULL)) {
        return NULL;
    }
    Py_RETURN_NONE;
}

static PyObject *
run_scalar_intp_converter(PyObject *NPY_UNUSED(self), PyObject *obj)
{
    PyArray_Dims dims;
    if (!PyArray_IntpConverter(obj, &dims)) {
        return NULL;
    }
    else {
        PyObject *result = PyArray_IntTupleFromIntp(dims.len, dims.ptr);
        PyDimMem_FREE(dims.ptr);
        return result;
    }
}

static PyObject *
run_scalar_intp_from_sequence(PyObject *NPY_UNUSED(self), PyObject *obj)
{
    npy_intp vals[1];

    int output = PyArray_IntpFromSequence(obj, vals, 1);
    if (output == -1) {
        return NULL;
    }
    return PyArray_IntTupleFromIntp(1, vals);
}

static PyMethodDef Multiarray_TestsMethods[] = {
    {"argparse_example_function",
         (PyCFunction)argparse_example_function,
         METH_KEYWORDS | METH_FASTCALL, NULL},
    {"threaded_argparse_example_function",
         (PyCFunction)threaded_argparse_example_function,
         METH_KEYWORDS | METH_FASTCALL, NULL},
    {"IsPythonScalar",
        IsPythonScalar,
        METH_VARARGS, NULL},
    {"test_neighborhood_iterator",
        test_neighborhood_iterator,
        METH_VARARGS, NULL},
    {"test_neighborhood_iterator_oob",
        test_neighborhood_iterator_oob,
        METH_VARARGS, NULL},
    {"fromstring_null_term_c_api",
        fromstring_null_term_c_api,
        METH_O, NULL},
    {"create_custom_field_dtype",
        create_custom_field_dtype,
        METH_VARARGS, NULL},
    {"corrupt_or_fix_bufferinfo",
        corrupt_or_fix_bufferinfo,
        METH_O, NULL},
    {"incref_elide",
        incref_elide,
        METH_VARARGS, NULL},
    {"incref_elide_l",
        incref_elide_l,
        METH_VARARGS, NULL},
    {"npy_char_deprecation",
        npy_char_deprecation,
        METH_NOARGS, NULL},
    {"npy_create_writebackifcopy",
        npy_create_writebackifcopy,
        METH_O, NULL},
    {"npy_abuse_writebackifcopy",
        npy_abuse_writebackifcopy,
        METH_O, NULL},
    {"npy_resolve",
        npy_resolve,
        METH_O, NULL},
    {"npy_discard",
        npy_discard,
        METH_O, NULL},
    {"npy_ensurenocopy",
        npy_ensurenocopy,
        METH_O, NULL},
    {"get_buffer_info",
        get_buffer_info,
        METH_VARARGS, NULL},
    {"get_c_wrapping_array",
        get_c_wrapping_array,
        METH_O, NULL},
    {"get_all_cast_information",
        get_all_cast_information,
        METH_NOARGS,
        "Return a list with info on all available casts. Some of the info"
        "may differ for an actual cast if it uses value-based casting "
        "(flexible types)."},
    {"identityhash_tester",
        (PyCFunction)identityhash_tester,
        METH_KEYWORDS | METH_FASTCALL, NULL},
    {"array_indexing",
        array_indexing,
        METH_VARARGS, NULL},
    {"test_as_c_array",
        test_as_c_array,
        METH_VARARGS, NULL},
    {"test_nditer_too_large",
        test_nditer_too_large,
        METH_VARARGS, NULL},
    {"solve_diophantine",
        (PyCFunction)array_solve_diophantine,
        METH_VARARGS | METH_KEYWORDS, NULL},
    {"internal_overlap",
        (PyCFunction)array_internal_overlap,
        METH_VARARGS | METH_KEYWORDS, NULL},
    {"extint_safe_binop",
        extint_safe_binop,
        METH_VARARGS, NULL},
    {"extint_to_128",
        extint_to_128,
        METH_VARARGS, NULL},
    {"extint_to_64",
        extint_to_64,
        METH_VARARGS, NULL},
    {"extint_mul_64_64",
        extint_mul_64_64,
        METH_VARARGS, NULL},
    {"extint_add_128",
        extint_add_128,
        METH_VARARGS, NULL},
    {"extint_sub_128",
        extint_sub_128,
        METH_VARARGS, NULL},
    {"extint_neg_128",
        extint_neg_128,
        METH_VARARGS, NULL},
    {"extint_shl_128",
        extint_shl_128,
        METH_VARARGS, NULL},
    {"extint_shr_128",
        extint_shr_128,
        METH_VARARGS, NULL},
    {"extint_gt_128",
        extint_gt_128,
        METH_VARARGS, NULL},
    {"extint_divmod_128_64",
        extint_divmod_128_64,
        METH_VARARGS, NULL},
    {"extint_floordiv_128_64",
        extint_floordiv_128_64,
        METH_VARARGS, NULL},
    {"extint_ceildiv_128_64",
        extint_ceildiv_128_64,
        METH_VARARGS, NULL},
    {"get_fpu_mode",
        get_fpu_mode,
        METH_VARARGS, get_fpu_mode_doc},
/**begin repeat
 * #name = cabs, carg#
 */

/**begin repeat1
 * #suffix = f, , l#
 */
    {"npy_@name@@suffix@",
        call_npy_@name@@suffix@,
        METH_VARARGS, NULL},
/**end repeat1**/

/**end repeat**/

/**begin repeat
 * #name = log10, cosh, sinh, tan, tanh#
 */

/**begin repeat1
 * #suffix= f, , l#
 */
    {"npy_@name@@suffix@",
        call_npy_@name@@suffix@,
        METH_VARARGS, NULL},
/**end repeat1**/

/**end repeat**/
    {"format_float_OSprintf_g",
        (PyCFunction)printf_float_g,
        METH_VARARGS , NULL},
    {"get_struct_alignments",
        get_struct_alignments,
        METH_VARARGS, NULL},
    {"run_byteorder_converter",
        run_byteorder_converter,
        METH_VARARGS, NULL},
    {"run_sortkind_converter",
        run_sortkind_converter,
        METH_VARARGS, NULL},
    {"run_selectkind_converter",
        run_selectkind_converter,
        METH_VARARGS, NULL},
    {"run_searchside_converter",
        run_searchside_converter,
        METH_VARARGS, NULL},
    {"run_order_converter",
        run_order_converter,
        METH_VARARGS, NULL},
    {"run_clipmode_converter",
        run_clipmode_converter,
        METH_VARARGS, NULL},
    {"run_casting_converter",
        run_casting_converter,
        METH_VARARGS, NULL},
    {"run_scalar_intp_converter",
        run_scalar_intp_converter,
        METH_O, NULL},
    {"run_scalar_intp_from_sequence",
        run_scalar_intp_from_sequence,
        METH_O, NULL},
    {"run_intp_converter",
        run_intp_converter,
        METH_VARARGS, NULL},
    {NULL, NULL, 0, NULL}        /* Sentinel */
};


static struct PyModuleDef moduledef = {
        PyModuleDef_HEAD_INIT,
        "_multiarray_tests",
        NULL,
        -1,
        Multiarray_TestsMethods,
        NULL,
        NULL,
        NULL,
        NULL
};

PyMODINIT_FUNC PyInit__multiarray_tests(void)
{
    PyObject *m;

    m = PyModule_Create(&moduledef);
    if (m == NULL) {
        return m;
    }
    import_array();
    if (init_argparse_mutex() < 0) {
        return NULL;
    }
    if (PyErr_Occurred()) {
        PyErr_SetString(PyExc_RuntimeError,
                        "cannot load _multiarray_tests module.");
    }

#if Py_GIL_DISABLED
    // signal this module supports running with the GIL disabled
    PyUnstable_Module_SetGIL(m, Py_MOD_GIL_NOT_USED);
#endif

    return m;
}

NPY_NO_EXPORT int
test_not_exported(void)
{
    return 1;
}
