28558dca80
Build / Build and test on ubuntu-24.04-arm for aarch64 (push) Waiting to run
Build / Build and test on windows-11-arm for aarch64 (push) Waiting to run
Build / Build and test on macos-latest for x86_64 (push) Waiting to run
Build / Build and test on windows-latest for x86_64 (push) Waiting to run
Build / Build and test on ubuntu-latest for x86_64 (push) Failing after 1s
Build / Build and test on ubuntu-latest (numpy 1.26) for x86_64 (push) Failing after 0s
408 lines
14 KiB
C++
408 lines
14 KiB
C++
/*********************************************************************
|
|
Numexpr - Fast numerical array expression evaluator for NumPy.
|
|
|
|
License: MIT
|
|
Author: See AUTHORS.txt
|
|
|
|
See LICENSE.txt for details about copyright and rights to use.
|
|
**********************************************************************/
|
|
|
|
#include "module.hpp"
|
|
#include <structmember.h>
|
|
|
|
#include "numexpr_config.hpp"
|
|
#include "interpreter.hpp"
|
|
#include "numexpr_object.hpp"
|
|
|
|
static int
|
|
size_from_char(char c)
|
|
{
|
|
switch (c) {
|
|
case 'b': return sizeof(char);
|
|
case 'i': return sizeof(int);
|
|
case 'l': return sizeof(long long);
|
|
case 'f': return sizeof(float);
|
|
case 'd': return sizeof(double);
|
|
case 'c': return 2*sizeof(double);
|
|
case 's': return 0; /* strings are ok but size must be computed */
|
|
default:
|
|
PyErr_SetString(PyExc_TypeError, "signature value not in 'bilfdcs'");
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
static void
|
|
NumExpr_dealloc(NumExprObject *self)
|
|
{
|
|
Py_XDECREF(self->signature);
|
|
Py_XDECREF(self->tempsig);
|
|
Py_XDECREF(self->constsig);
|
|
Py_XDECREF(self->fullsig);
|
|
Py_XDECREF(self->program);
|
|
Py_XDECREF(self->constants);
|
|
Py_XDECREF(self->input_names);
|
|
PyMem_Del(self->mem);
|
|
PyMem_Del(self->rawmem);
|
|
PyMem_Del(self->memsteps);
|
|
PyMem_Del(self->memsizes);
|
|
Py_TYPE(self)->tp_free((PyObject*)self);
|
|
}
|
|
|
|
static PyObject *
|
|
NumExpr_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
|
|
{
|
|
NumExprObject *self = (NumExprObject *)type->tp_alloc(type, 0);
|
|
if (self != NULL) {
|
|
#define INIT_WITH(name, object) \
|
|
self->name = object; \
|
|
if (!self->name) { \
|
|
Py_DECREF(self); \
|
|
return NULL; \
|
|
}
|
|
|
|
INIT_WITH(signature, PyBytes_FromString(""));
|
|
INIT_WITH(tempsig, PyBytes_FromString(""));
|
|
INIT_WITH(constsig, PyBytes_FromString(""));
|
|
INIT_WITH(fullsig, PyBytes_FromString(""));
|
|
INIT_WITH(program, PyBytes_FromString(""));
|
|
INIT_WITH(constants, PyTuple_New(0));
|
|
Py_INCREF(Py_None);
|
|
self->input_names = Py_None;
|
|
self->mem = NULL;
|
|
self->rawmem = NULL;
|
|
self->memsteps = NULL;
|
|
self->memsizes = NULL;
|
|
self->rawmemsize = 0;
|
|
self->n_inputs = 0;
|
|
self->n_constants = 0;
|
|
self->n_temps = 0;
|
|
#undef INIT_WITH
|
|
}
|
|
return (PyObject *)self;
|
|
}
|
|
|
|
#define CHARP(s) ((char *)(s))
|
|
|
|
static int
|
|
NumExpr_init(NumExprObject *self, PyObject *args, PyObject *kwds)
|
|
{
|
|
int i, j, mem_offset;
|
|
int n_inputs, n_constants, n_temps;
|
|
PyObject *signature = NULL, *tempsig = NULL, *constsig = NULL;
|
|
PyObject *fullsig = NULL, *program = NULL, *constants = NULL;
|
|
PyObject *input_names = NULL, *o_constants = NULL;
|
|
int *itemsizes = NULL;
|
|
char **mem = NULL, *rawmem = NULL;
|
|
npy_intp *memsteps;
|
|
npy_intp *memsizes;
|
|
int rawmemsize;
|
|
static char *kwlist[] = {CHARP("signature"), CHARP("tempsig"),
|
|
CHARP("program"), CHARP("constants"),
|
|
CHARP("input_names"), NULL};
|
|
|
|
if (!PyArg_ParseTupleAndKeywords(args, kwds, "SSS|OO", kwlist,
|
|
&signature,
|
|
&tempsig,
|
|
&program, &o_constants,
|
|
&input_names)) {
|
|
return -1;
|
|
}
|
|
|
|
n_inputs = (int)PyBytes_Size(signature);
|
|
n_temps = (int)PyBytes_Size(tempsig);
|
|
|
|
if (o_constants) {
|
|
if (!PySequence_Check(o_constants) ) {
|
|
PyErr_SetString(PyExc_TypeError, "constants must be a sequence");
|
|
return -1;
|
|
}
|
|
n_constants = (int)PySequence_Length(o_constants);
|
|
if (!(constants = PyTuple_New(n_constants)))
|
|
return -1;
|
|
if (!(constsig = PyBytes_FromStringAndSize(NULL, n_constants))) {
|
|
Py_DECREF(constants);
|
|
return -1;
|
|
}
|
|
if (!(itemsizes = PyMem_New(int, n_constants))) {
|
|
Py_DECREF(constants);
|
|
return -1;
|
|
}
|
|
for (i = 0; i < n_constants; i++) {
|
|
PyObject *o;
|
|
if (!(o = PySequence_GetItem(o_constants, i))) { /* new reference */
|
|
Py_DECREF(constants);
|
|
Py_DECREF(constsig);
|
|
PyMem_Del(itemsizes);
|
|
return -1;
|
|
}
|
|
PyTuple_SET_ITEM(constants, i, o); /* steals reference */
|
|
if (PyBool_Check(o)) {
|
|
PyBytes_AS_STRING(constsig)[i] = 'b';
|
|
itemsizes[i] = size_from_char('b');
|
|
continue;
|
|
}
|
|
|
|
if (PyArray_IsScalar(o, Int32)) {
|
|
PyBytes_AS_STRING(constsig)[i] = 'i';
|
|
itemsizes[i] = size_from_char('i');
|
|
continue;
|
|
}
|
|
|
|
if (PyArray_IsScalar(o, Int64)) {
|
|
PyBytes_AS_STRING(constsig)[i] = 'l';
|
|
itemsizes[i] = size_from_char('l');
|
|
continue;
|
|
}
|
|
/* The Float32 scalars are the only ones that should reach here */
|
|
if (PyArray_IsScalar(o, Float32)) {
|
|
PyBytes_AS_STRING(constsig)[i] = 'f';
|
|
itemsizes[i] = size_from_char('f');
|
|
continue;
|
|
}
|
|
if (PyFloat_Check(o)) {
|
|
/* Python float constants are double precision by default */
|
|
PyBytes_AS_STRING(constsig)[i] = 'd';
|
|
itemsizes[i] = size_from_char('d');
|
|
continue;
|
|
}
|
|
if (PyComplex_Check(o)) {
|
|
PyBytes_AS_STRING(constsig)[i] = 'c';
|
|
itemsizes[i] = size_from_char('c');
|
|
continue;
|
|
}
|
|
if (PyBytes_Check(o)) {
|
|
PyBytes_AS_STRING(constsig)[i] = 's';
|
|
itemsizes[i] = (int)PyBytes_GET_SIZE(o);
|
|
continue;
|
|
}
|
|
PyErr_SetString(PyExc_TypeError, "constants must be of type bool/int/long/float/double/complex/bytes");
|
|
Py_DECREF(constsig);
|
|
Py_DECREF(constants);
|
|
PyMem_Del(itemsizes);
|
|
return -1;
|
|
}
|
|
} else {
|
|
n_constants = 0;
|
|
if (!(constants = PyTuple_New(0)))
|
|
return -1;
|
|
if (!(constsig = PyBytes_FromString(""))) {
|
|
Py_DECREF(constants);
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
fullsig = PyBytes_FromFormat("%c%s%s%s", get_return_sig(program),
|
|
PyBytes_AS_STRING(signature), PyBytes_AS_STRING(constsig),
|
|
PyBytes_AS_STRING(tempsig));
|
|
if (!fullsig) {
|
|
Py_DECREF(constants);
|
|
Py_DECREF(constsig);
|
|
PyMem_Del(itemsizes);
|
|
return -1;
|
|
}
|
|
|
|
if (!input_names) {
|
|
input_names = Py_None;
|
|
}
|
|
|
|
/* Compute the size of registers. We leave temps out (will be
|
|
malloc'ed later on). */
|
|
rawmemsize = 0;
|
|
for (i = 0; i < n_constants; i++)
|
|
rawmemsize += itemsizes[i];
|
|
rawmemsize *= BLOCK_SIZE1;
|
|
|
|
mem = PyMem_New(char *, 1 + n_inputs + n_constants + n_temps);
|
|
rawmem = PyMem_New(char, rawmemsize);
|
|
memsteps = PyMem_New(npy_intp, 1 + n_inputs + n_constants + n_temps);
|
|
memsizes = PyMem_New(npy_intp, 1 + n_inputs + n_constants + n_temps);
|
|
if (!mem || !rawmem || !memsteps || !memsizes) {
|
|
Py_DECREF(constants);
|
|
Py_DECREF(constsig);
|
|
Py_DECREF(fullsig);
|
|
PyMem_Del(itemsizes);
|
|
PyMem_Del(mem);
|
|
PyMem_Del(rawmem);
|
|
PyMem_Del(memsteps);
|
|
PyMem_Del(memsizes);
|
|
return -1;
|
|
}
|
|
/*
|
|
0 -> output
|
|
[1, n_inputs+1) -> inputs
|
|
[n_inputs+1, n_inputs+n_consts+1) -> constants
|
|
[n_inputs+n_consts+1, n_inputs+n_consts+n_temps+1) -> temps
|
|
*/
|
|
/* Fill in 'mem' and 'rawmem' for constants */
|
|
mem_offset = 0;
|
|
for (i = 0; i < n_constants; i++) {
|
|
char c = PyBytes_AS_STRING(constsig)[i];
|
|
int size = itemsizes[i];
|
|
mem[i+n_inputs+1] = rawmem + mem_offset;
|
|
mem_offset += BLOCK_SIZE1 * size;
|
|
memsteps[i+n_inputs+1] = memsizes[i+n_inputs+1] = size;
|
|
/* fill in the constants */
|
|
if (c == 'b') {
|
|
char *bmem = (char*)mem[i+n_inputs+1];
|
|
char value = (char)PyLong_AsLong(PyTuple_GET_ITEM(constants, i));
|
|
for (j = 0; j < BLOCK_SIZE1; j++) {
|
|
bmem[j] = value;
|
|
}
|
|
} else if (c == 'i') {
|
|
int *imem = (int*)mem[i+n_inputs+1];
|
|
int value = (int)PyLong_AsLong(PyTuple_GET_ITEM(constants, i));
|
|
for (j = 0; j < BLOCK_SIZE1; j++) {
|
|
imem[j] = value;
|
|
}
|
|
} else if (c == 'l') {
|
|
long long *lmem = (long long*)mem[i+n_inputs+1];
|
|
long long value = PyLong_AsLongLong(PyTuple_GET_ITEM(constants, i));
|
|
for (j = 0; j < BLOCK_SIZE1; j++) {
|
|
lmem[j] = value;
|
|
}
|
|
} else if (c == 'f') {
|
|
/* In this particular case the constant is in a NumPy scalar
|
|
and in a regular Python object */
|
|
float *fmem = (float*)mem[i+n_inputs+1];
|
|
float value = PyArrayScalar_VAL(PyTuple_GET_ITEM(constants, i),
|
|
Float);
|
|
for (j = 0; j < BLOCK_SIZE1; j++) {
|
|
fmem[j] = value;
|
|
}
|
|
} else if (c == 'd') {
|
|
double *dmem = (double*)mem[i+n_inputs+1];
|
|
double value = PyFloat_AS_DOUBLE(PyTuple_GET_ITEM(constants, i));
|
|
for (j = 0; j < BLOCK_SIZE1; j++) {
|
|
dmem[j] = value;
|
|
}
|
|
} else if (c == 'c') {
|
|
double *cmem = (double*)mem[i+n_inputs+1];
|
|
Py_complex value = PyComplex_AsCComplex(PyTuple_GET_ITEM(constants, i));
|
|
for (j = 0; j < 2*BLOCK_SIZE1; j+=2) {
|
|
cmem[j] = value.real;
|
|
cmem[j+1] = value.imag;
|
|
}
|
|
} else if (c == 's') {
|
|
char *smem = (char*)mem[i+n_inputs+1];
|
|
char *value = PyBytes_AS_STRING(PyTuple_GET_ITEM(constants, i));
|
|
for (j = 0; j < size*BLOCK_SIZE1; j+=size) {
|
|
memcpy(smem + j, value, size);
|
|
}
|
|
}
|
|
}
|
|
/* This is no longer needed since no unusual item sizes appear
|
|
in temporaries (there are no string temporaries). */
|
|
PyMem_Del(itemsizes);
|
|
|
|
/* Fill in 'memsteps' and 'memsizes' for temps */
|
|
for (i = 0; i < n_temps; i++) {
|
|
char c = PyBytes_AS_STRING(tempsig)[i];
|
|
int size = size_from_char(c);
|
|
memsteps[i+n_inputs+n_constants+1] = size;
|
|
memsizes[i+n_inputs+n_constants+1] = size;
|
|
}
|
|
/* See if any errors occured (e.g., in size_from_char) or if mem_offset is wrong */
|
|
if (PyErr_Occurred() || mem_offset != rawmemsize) {
|
|
if (mem_offset != rawmemsize) {
|
|
PyErr_Format(PyExc_RuntimeError, "mem_offset does not match rawmemsize");
|
|
}
|
|
Py_DECREF(constants);
|
|
Py_DECREF(constsig);
|
|
Py_DECREF(fullsig);
|
|
PyMem_Del(mem);
|
|
PyMem_Del(rawmem);
|
|
PyMem_Del(memsteps);
|
|
PyMem_Del(memsizes);
|
|
return -1;
|
|
}
|
|
|
|
|
|
#define REPLACE_OBJ(arg) \
|
|
{PyObject *tmp = self->arg; \
|
|
self->arg = arg; \
|
|
Py_XDECREF(tmp);}
|
|
#define INCREF_REPLACE_OBJ(arg) {Py_INCREF(arg); REPLACE_OBJ(arg);}
|
|
#define REPLACE_MEM(arg) {PyMem_Del(self->arg); self->arg=arg;}
|
|
|
|
INCREF_REPLACE_OBJ(signature);
|
|
INCREF_REPLACE_OBJ(tempsig);
|
|
REPLACE_OBJ(constsig);
|
|
REPLACE_OBJ(fullsig);
|
|
INCREF_REPLACE_OBJ(program);
|
|
REPLACE_OBJ(constants);
|
|
INCREF_REPLACE_OBJ(input_names);
|
|
REPLACE_MEM(mem);
|
|
REPLACE_MEM(rawmem);
|
|
REPLACE_MEM(memsteps);
|
|
REPLACE_MEM(memsizes);
|
|
self->rawmemsize = rawmemsize;
|
|
self->n_inputs = n_inputs;
|
|
self->n_constants = n_constants;
|
|
self->n_temps = n_temps;
|
|
|
|
#undef REPLACE_OBJ
|
|
#undef INCREF_REPLACE_OBJ
|
|
#undef REPLACE_MEM
|
|
|
|
return check_program(self);
|
|
}
|
|
|
|
static PyMethodDef NumExpr_methods[] = {
|
|
{"run", (PyCFunction) NumExpr_run, METH_VARARGS|METH_KEYWORDS, NULL},
|
|
{NULL, NULL}
|
|
};
|
|
|
|
static PyMemberDef NumExpr_members[] = {
|
|
{CHARP("signature"), T_OBJECT_EX, offsetof(NumExprObject, signature), READONLY, NULL},
|
|
{CHARP("constsig"), T_OBJECT_EX, offsetof(NumExprObject, constsig), READONLY, NULL},
|
|
{CHARP("tempsig"), T_OBJECT_EX, offsetof(NumExprObject, tempsig), READONLY, NULL},
|
|
{CHARP("fullsig"), T_OBJECT_EX, offsetof(NumExprObject, fullsig), READONLY, NULL},
|
|
|
|
{CHARP("program"), T_OBJECT_EX, offsetof(NumExprObject, program), READONLY, NULL},
|
|
{CHARP("constants"), T_OBJECT_EX, offsetof(NumExprObject, constants),
|
|
READONLY, NULL},
|
|
{CHARP("input_names"), T_OBJECT, offsetof(NumExprObject, input_names), 0, NULL},
|
|
{NULL},
|
|
};
|
|
|
|
PyTypeObject NumExprType = {
|
|
PyVarObject_HEAD_INIT(NULL, 0)
|
|
"numexpr.NumExpr", /*tp_name*/
|
|
sizeof(NumExprObject), /*tp_basicsize*/
|
|
0, /*tp_itemsize*/
|
|
(destructor)NumExpr_dealloc, /*tp_dealloc*/
|
|
0, /*tp_print*/
|
|
0, /*tp_getattr*/
|
|
0, /*tp_setattr*/
|
|
0, /*tp_compare*/
|
|
0, /*tp_repr*/
|
|
0, /*tp_as_number*/
|
|
0, /*tp_as_sequence*/
|
|
0, /*tp_as_mapping*/
|
|
0, /*tp_hash */
|
|
(ternaryfunc)NumExpr_run, /*tp_call*/
|
|
0, /*tp_str*/
|
|
0, /*tp_getattro*/
|
|
0, /*tp_setattro*/
|
|
0, /*tp_as_buffer*/
|
|
Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /*tp_flags*/
|
|
"NumExpr objects", /* tp_doc */
|
|
0, /* tp_traverse */
|
|
0, /* tp_clear */
|
|
0, /* tp_richcompare */
|
|
0, /* tp_weaklistoffset */
|
|
0, /* tp_iter */
|
|
0, /* tp_iternext */
|
|
NumExpr_methods, /* tp_methods */
|
|
NumExpr_members, /* tp_members */
|
|
0, /* tp_getset */
|
|
0, /* tp_base */
|
|
0, /* tp_dict */
|
|
0, /* tp_descr_get */
|
|
0, /* tp_descr_set */
|
|
0, /* tp_dictoffset */
|
|
(initproc)NumExpr_init, /* tp_init */
|
|
0, /* tp_alloc */
|
|
NumExpr_new, /* tp_new */
|
|
};
|