Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
98 changes: 29 additions & 69 deletions numpy/_core/src/common/npy_argparse.c
Original file line number Diff line number Diff line change
Expand Up @@ -76,8 +76,6 @@ PyArray_PythonPyIntFromInt(PyObject *obj, int *value)
}


typedef int convert(PyObject *, void *);

/**
* Internal function to initialize keyword argument parsing.
*
Expand All @@ -92,55 +90,41 @@ typedef int convert(PyObject *, void *);
*
* @param funcname Name of the function, mainly used for errors.
* @param cache A cache object stored statically in the parsing function
* @param va_orig Argument list to npy_parse_arguments
* @param specs Array of argument specifications
* @param nspecs Number of argument specifications
* @return 0 on success, -1 on failure
*/
static int
initialize_keywords(const char *funcname,
_NpyArgParserCache *cache, va_list va_orig) {
va_list va;
int nargs = 0;
_NpyArgParserCache *cache, npy_arg_spec *specs, int nspecs) {
int nkwargs = 0;
int npositional_only = 0;
int nrequired = 0;
int npositional = 0;
char state = '\0';

va_copy(va, va_orig);
while (1) {
/* Count length first: */
char *name = va_arg(va, char *);
convert *converter = va_arg(va, convert *);
void *data = va_arg(va, void *);

/* Check if this is the sentinel, only converter may be NULL */
if ((name == NULL) && (converter == NULL) && (data == NULL)) {
break;
}
for (int i = 0; i < nspecs; i++) {
const char *name = specs[i].name;

if (name == NULL) {
PyErr_Format(PyExc_SystemError,
"NumPy internal error: name is NULL in %s() at "
"argument %d.", funcname, nargs);
va_end(va);
"argument %d.", funcname, i);
return -1;
}
if (data == NULL) {
if (specs[i].output == NULL) {
PyErr_Format(PyExc_SystemError,
"NumPy internal error: data is NULL in %s() at "
"argument %d.", funcname, nargs);
va_end(va);
"argument %d.", funcname, i);
return -1;
}

nargs += 1;
if (*name == '|') {
if (state == '$') {
PyErr_Format(PyExc_SystemError,
"NumPy internal error: positional argument `|` "
"after keyword only `$` one to %s() at argument %d.",
funcname, nargs);
va_end(va);
funcname, i + 1);
return -1;
}
state = '|';
Expand All @@ -156,8 +140,7 @@ initialize_keywords(const char *funcname,
PyErr_Format(PyExc_SystemError,
"NumPy internal error: non-required argument after "
"required | or $ one to %s() at argument %d.",
funcname, nargs);
va_end(va);
funcname, i + 1);
return -1;
}

Expand All @@ -172,48 +155,42 @@ initialize_keywords(const char *funcname,
PyErr_Format(PyExc_SystemError,
"NumPy internal error: non-kwarg marked with $ "
"to %s() at argument %d or positional only following "
"kwarg.", funcname, nargs);
va_end(va);
"kwarg.", funcname, i + 1);
return -1;
}
}
else {
nkwargs += 1;
}
}
va_end(va);

if (npositional == -1) {
npositional = nargs;
npositional = nspecs;
}

if (nargs > _NPY_MAX_KWARGS) {
if (nspecs > _NPY_MAX_KWARGS) {
PyErr_Format(PyExc_SystemError,
"NumPy internal error: function %s() has %d arguments, but "
"the maximum is currently limited to %d for easier parsing; "
"it can be increased by modifying `_NPY_MAX_KWARGS`.",
funcname, nargs, _NPY_MAX_KWARGS);
funcname, nspecs, _NPY_MAX_KWARGS);
return -1;
}

/*
* Do any necessary string allocation and interning,
* creating a caching object.
*/
cache->nargs = nargs;
cache->nargs = nspecs;
cache->npositional_only = npositional_only;
cache->npositional = npositional;
cache->nrequired = nrequired;

/* NULL kw_strings for easier cleanup (and NULL termination) */
memset(cache->kw_strings, 0, sizeof(PyObject *) * (nkwargs + 1));

va_copy(va, va_orig);
for (int i = 0; i < nargs; i++) {
/* Advance through non-kwargs, which do not require setup. */
char *name = va_arg(va, char *);
va_arg(va, convert *);
va_arg(va, void *);
for (int i = 0; i < nspecs; i++) {
const char *name = specs[i].name;

if (*name == '|' || *name == '$') {
name++; /* ignore | and $ */
Expand All @@ -222,13 +199,11 @@ initialize_keywords(const char *funcname,
int i_kwarg = i - npositional_only;
cache->kw_strings[i_kwarg] = PyUnicode_InternFromString(name);
if (cache->kw_strings[i_kwarg] == NULL) {
va_end(va);
goto error;
}
}
}

va_end(va);
return 0;

error:
Expand Down Expand Up @@ -288,25 +263,21 @@ raise_missing_argument(const char *funcname,
* @param args Python passed args (METH_FASTCALL)
* @param len_args Number of arguments (not flagged)
* @param kwnames Tuple as passed by METH_FASTCALL or NULL.
* @param ... List of arguments (see macro version).
* @param specs Array of argument specifications
* @param nspecs Number of argument specifications
*
* @return Returns 0 on success and -1 on failure.
*/
NPY_NO_EXPORT int
_npy_parse_arguments(const char *funcname,
/* cache_ptr is a NULL initialized persistent storage for data */
_NpyArgParserCache *cache,
PyObject *const *args, Py_ssize_t len_args, PyObject *kwnames,
/* ... is NULL, NULL, NULL terminated: name, converter, value */
...)
npy_arg_spec *specs, int nspecs)
{
if (!atomic_load_explicit((_Atomic(uint8_t) *)&cache->initialized, memory_order_acquire)) {
LOCK_ARGPARSE_MUTEX;
if (!atomic_load_explicit((_Atomic(uint8_t) *)&cache->initialized, memory_order_acquire)) {
va_list va;
va_start(va, kwnames);
int res = initialize_keywords(funcname, cache, va);
va_end(va);
int res = initialize_keywords(funcname, cache, specs, nspecs);
if (res < 0) {
UNLOCK_ARGPARSE_MUTEX;
return -1;
Expand Down Expand Up @@ -394,38 +365,33 @@ _npy_parse_arguments(const char *funcname,
assert(len_args + len_kwargs <= cache->nargs);

/* At this time `all_arguments` holds either NULLs or the objects */
va_list va;
va_start(va, kwnames);

for (int i = 0; i < max_nargs; i++) {
va_arg(va, char *);
convert *converter = va_arg(va, convert *);
void *data = va_arg(va, void *);

if (all_arguments[i] == NULL) {
continue;
}

int res;
npy_arg_converter converter = (npy_arg_converter)specs[i].converter;
void *data = specs[i].output;

if (converter == NULL) {
*((PyObject **) data) = all_arguments[i];
continue;
}
res = converter(all_arguments[i], data);
int res = converter(all_arguments[i], data);

if (NPY_UNLIKELY(res == NPY_SUCCEED)) {
continue;
}
else if (NPY_UNLIKELY(res == NPY_FAIL)) {
/* It is usually the users responsibility to clean up. */
goto converting_failed;
return -1;
}
else if (NPY_UNLIKELY(res == Py_CLEANUP_SUPPORTED)) {
/* TODO: Implementing cleanup if/when needed should not be hard */
PyErr_Format(PyExc_SystemError,
"converter cleanup of parameter %d to %s() not supported.",
i, funcname);
goto converting_failed;
return -1;
}
assert(0);
}
Expand All @@ -435,21 +401,15 @@ _npy_parse_arguments(const char *funcname,
/* (PyArg_* also does this after the actual parsing is finished) */
if (NPY_UNLIKELY(max_nargs < cache->nrequired)) {
raise_missing_argument(funcname, cache, max_nargs);
goto converting_failed;
return -1;
}
for (int i = 0; i < cache->nrequired; i++) {
if (NPY_UNLIKELY(all_arguments[i] == NULL)) {
raise_missing_argument(funcname, cache, i);
goto converting_failed;
return -1;
}
}
}

va_end(va);
return 0;

converting_failed:
va_end(va);
return -1;

}
58 changes: 38 additions & 20 deletions numpy/_core/src/common/npy_argparse.h
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,15 @@
NPY_NO_EXPORT int
PyArray_PythonPyIntFromInt(PyObject *obj, int *value);

#define _NPY_MAX_KWARGS 15
#define _NPY_MAX_KWARGS 14

typedef int (*npy_arg_converter)(PyObject *, void *);

typedef struct {
const char *name;
void *converter;
void *output;
} npy_arg_spec;

typedef struct {
int npositional;
Expand Down Expand Up @@ -54,44 +62,54 @@ NPY_NO_EXPORT int init_argparse_mutex(void);
*
* PyObject *argument1, *argument3;
* int argument2 = -1;
* if (npy_parse_arguments("method", args, len_args, kwnames),
* "argument1", NULL, &argument1,
* "|argument2", &PyArray_PythonPyIntFromInt, &argument2,
* "$argument3", NULL, &argument3,
* NULL, NULL, NULL) < 0) {
* if (npy_parse_arguments("method", args, len_args, kwnames,
* {"argument1", NULL, &argument1},
* {"|argument2", &PyArray_PythonPyIntFromInt, &argument2},
* {"$argument3", NULL, &argument3}) < 0) {
* return NULL;
* }
* }
* @endcode
*
* The `NPY_PREPARE_ARGPARSER` macro sets up a static cache variable necessary
* to hold data for speeding up the parsing. `npy_parse_arguments` must be
* used in cunjunction with the macro defined in the same scope.
* used in conjunction with the macro defined in the same scope.
* (No two `npy_parse_arguments` may share a single `NPY_PREPARE_ARGPARSER`.)
*
* @param funcname Function name
* @param args Python passed args (METH_FASTCALL)
* @param len_args Number of arguments (not flagged)
* @param kwnames Tuple as passed by METH_FASTCALL or NULL.
* @param ... List of arguments must be param1_name, param1_converter,
* *param1_outvalue, param2_name, ..., NULL, NULL, NULL.
* Where name is ``char *``, ``converter`` a python converter
* function or NULL and ``outvalue`` is the ``void *`` passed to
* the converter (holding the converted data or a borrowed
* reference if converter is NULL).
* @param ... List of argument specs as {name, converter, outvalue} structs.
* Where name is ``const char *``, ``converter`` a python converter
* function pointer or NULL and ``outvalue`` is the ``void *``
* passed to the converter (holding the converted data or a
* borrowed reference if converter is NULL).
*
* @return Returns 0 on success and -1 on failure.
*/
NPY_NO_EXPORT int
_npy_parse_arguments(const char *funcname,
/* cache_ptr is a NULL initialized persistent storage for data */
_NpyArgParserCache *cache_ptr,
_NpyArgParserCache *cache,
PyObject *const *args, Py_ssize_t len_args, PyObject *kwnames,
/* va_list is NULL, NULL, NULL terminated: name, converter, value */
...) NPY_GCC_NONNULL(1);
npy_arg_spec *specs, int nspecs) NPY_GCC_NONNULL(1);

#define npy_parse_arguments(funcname, args, len_args, kwnames, ...) \
_npy_parse_arguments(funcname, &__argparse_cache, \
args, len_args, kwnames, __VA_ARGS__)
#ifdef __cplusplus
#define npy_parse_arguments(funcname, args, len_args, kwnames, ...) \
[&]() -> int { \
npy_arg_spec _npy_specs_[] = {__VA_ARGS__}; \
return _npy_parse_arguments(funcname, &__argparse_cache, \
args, len_args, kwnames, \
_npy_specs_, \
(int)(sizeof(_npy_specs_) / sizeof(npy_arg_spec))); \
}()
#else
#define npy_parse_arguments(funcname, args, len_args, kwnames, ...) \
_npy_parse_arguments(funcname, &__argparse_cache, \
args, len_args, kwnames, \
(npy_arg_spec[]){__VA_ARGS__}, \
(int)(sizeof((npy_arg_spec[]){__VA_ARGS__}) \
/ sizeof(npy_arg_spec)))
#endif

#endif /* NUMPY_CORE_SRC_COMMON_NPY_ARGPARSE_H */
14 changes: 6 additions & 8 deletions numpy/_core/src/multiarray/_multiarray_tests.c.src
Original file line number Diff line number Diff line change
Expand Up @@ -33,11 +33,10 @@ argparse_example_function(PyObject *NPY_UNUSED(mod),
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) {
{"", &PyArray_PythonPyIntFromInt, &arg1},
{"arg2", NULL, &arg2},
{"|arg3", NULL, &arg3},
{"$arg3", NULL, &arg4}) < 0) {
return NULL;
}
Py_RETURN_NONE;
Expand All @@ -57,9 +56,8 @@ threaded_argparse_example_function(PyObject *NPY_UNUSED(mod),
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) {
{"$arg1", &PyArray_PythonPyIntFromInt, &arg1},
{"$arg2", NULL, &arg2}) < 0) {
return NULL;
}
Py_RETURN_NONE;
Expand Down
5 changes: 2 additions & 3 deletions numpy/_core/src/multiarray/array_coercion.c
Original file line number Diff line number Diff line change
Expand Up @@ -1420,9 +1420,8 @@ _discover_array_parameters(PyObject *NPY_UNUSED(self),
NPY_PREPARE_ARGPARSER;
if (npy_parse_arguments(
"_discover_array_parameters", args, len_args, kwnames,
"", NULL, &obj,
"|dtype", &PyArray_DTypeOrDescrConverterOptional, &dt_info,
NULL, NULL, NULL) < 0) {
{"", NULL, &obj},
{"|dtype", &PyArray_DTypeOrDescrConverterOptional, &dt_info}) < 0) {
/* fixed is last to parse, so never necessary to clean up */
return NULL;
}
Expand Down
Loading