Skip to content

Commit 2a9b913

Browse files
authored
Merge pull request numpy#26368 from ngoldbaum/nogil-thread-local-promotion-state
2 parents 67a04b1 + 18a3404 commit 2a9b913

File tree

12 files changed

+111
-44
lines changed

12 files changed

+111
-44
lines changed

numpy/_core/config.h.in

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88
#mesondefine HAVE_FSEEKO
99
#mesondefine HAVE_FALLOCATE
1010
#mesondefine HAVE_STRTOLD_L
11+
#mesondefine HAVE_THREAD_LOCAL
12+
#mesondefine HAVE__THREAD_LOCAL
1113
#mesondefine HAVE__THREAD
1214
#mesondefine HAVE___DECLSPEC_THREAD_
1315

numpy/_core/include/numpy/npy_common.h

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -113,14 +113,18 @@
113113
#define NPY_NOINLINE static
114114
#endif
115115

116-
#ifdef HAVE___THREAD
116+
#ifdef __cplusplus
117+
#define NPY_TLS thread_local
118+
#elif defined(HAVE_THREAD_LOCAL)
119+
#define NPY_TLS thread_local
120+
#elif defined(HAVE__THREAD_LOCAL)
121+
#define NPY_TLS _Thread_local
122+
#elif defined(HAVE___THREAD)
117123
#define NPY_TLS __thread
124+
#elif defined(HAVE___DECLSPEC_THREAD_)
125+
#define NPY_TLS __declspec(thread)
118126
#else
119-
#ifdef HAVE___DECLSPEC_THREAD_
120-
#define NPY_TLS __declspec(thread)
121-
#else
122-
#define NPY_TLS
123-
#endif
127+
#define NPY_TLS
124128
#endif
125129

126130
#ifdef WITH_CPYCHECKER_RETURNS_BORROWED_REF_ATTRIBUTE

numpy/_core/meson.build

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -244,6 +244,8 @@ endforeach
244244

245245
# variable attributes tested via "int %s a" % attribute
246246
optional_variable_attributes = [
247+
['thread_local', 'HAVE_THREAD_LOCAL'],
248+
['_Thread_local', 'HAVE__THREAD_LOCAL'],
247249
['__thread', 'HAVE__THREAD'],
248250
['__declspec(thread)', 'HAVE___DECLSPEC_THREAD_']
249251
]
@@ -262,7 +264,7 @@ foreach optional_attr: optional_variable_attributes
262264
return 0;
263265
}
264266
'''
265-
if cc.compiles(code)
267+
if cc.compiles(code, name: optional_attr[0])
266268
cdata.set10(optional_attr[1], true)
267269
endif
268270
endforeach

numpy/_core/src/multiarray/arraytypes.c.src

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -274,10 +274,10 @@ static int
274274
#endif
275275
) {
276276
PyArray_Descr *descr = PyArray_DescrFromType(NPY_@TYPE@);
277-
278-
if (npy_promotion_state == NPY_USE_LEGACY_PROMOTION || (
279-
npy_promotion_state == NPY_USE_WEAK_PROMOTION_AND_WARN
280-
&& !npy_give_promotion_warnings())) {
277+
int promotion_state = get_npy_promotion_state();
278+
if (promotion_state == NPY_USE_LEGACY_PROMOTION || (
279+
promotion_state == NPY_USE_WEAK_PROMOTION_AND_WARN
280+
&& !npy_give_promotion_warnings())) {
281281
/*
282282
* This path will be taken both for the "promotion" case such as
283283
* `uint8_arr + 123` as well as the assignment case.

numpy/_core/src/multiarray/convert_datatype.c

Lines changed: 28 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -51,11 +51,22 @@ NPY_NO_EXPORT npy_intp REQUIRED_STR_LEN[] = {0, 3, 5, 10, 10, 20, 20, 20, 20};
5151
/*
5252
* Whether or not legacy value-based promotion/casting is used.
5353
*/
54-
NPY_NO_EXPORT int npy_promotion_state = NPY_USE_LEGACY_PROMOTION;
54+
5555
NPY_NO_EXPORT PyObject *NO_NEP50_WARNING_CTX = NULL;
5656
NPY_NO_EXPORT PyObject *npy_DTypePromotionError = NULL;
5757
NPY_NO_EXPORT PyObject *npy_UFuncNoLoopError = NULL;
5858

59+
static NPY_TLS int npy_promotion_state = NPY_USE_LEGACY_PROMOTION;
60+
61+
NPY_NO_EXPORT int
62+
get_npy_promotion_state() {
63+
return npy_promotion_state;
64+
}
65+
66+
NPY_NO_EXPORT void
67+
set_npy_promotion_state(int new_promotion_state) {
68+
npy_promotion_state = new_promotion_state;
69+
}
5970

6071
static PyObject *
6172
PyArray_GetGenericToVoidCastingImpl(void);
@@ -100,13 +111,14 @@ npy_give_promotion_warnings(void)
100111

101112
NPY_NO_EXPORT PyObject *
102113
npy__get_promotion_state(PyObject *NPY_UNUSED(mod), PyObject *NPY_UNUSED(arg)) {
103-
if (npy_promotion_state == NPY_USE_WEAK_PROMOTION) {
114+
int promotion_state = get_npy_promotion_state();
115+
if (promotion_state == NPY_USE_WEAK_PROMOTION) {
104116
return PyUnicode_FromString("weak");
105117
}
106-
else if (npy_promotion_state == NPY_USE_WEAK_PROMOTION_AND_WARN) {
118+
else if (promotion_state == NPY_USE_WEAK_PROMOTION_AND_WARN) {
107119
return PyUnicode_FromString("weak_and_warn");
108120
}
109-
else if (npy_promotion_state == NPY_USE_LEGACY_PROMOTION) {
121+
else if (promotion_state == NPY_USE_LEGACY_PROMOTION) {
110122
return PyUnicode_FromString("legacy");
111123
}
112124
PyErr_SetString(PyExc_SystemError, "invalid promotion state!");
@@ -123,21 +135,23 @@ npy__set_promotion_state(PyObject *NPY_UNUSED(mod), PyObject *arg)
123135
"must be a string.");
124136
return NULL;
125137
}
138+
int new_promotion_state;
126139
if (PyUnicode_CompareWithASCIIString(arg, "weak") == 0) {
127-
npy_promotion_state = NPY_USE_WEAK_PROMOTION;
140+
new_promotion_state = NPY_USE_WEAK_PROMOTION;
128141
}
129142
else if (PyUnicode_CompareWithASCIIString(arg, "weak_and_warn") == 0) {
130-
npy_promotion_state = NPY_USE_WEAK_PROMOTION_AND_WARN;
143+
new_promotion_state = NPY_USE_WEAK_PROMOTION_AND_WARN;
131144
}
132145
else if (PyUnicode_CompareWithASCIIString(arg, "legacy") == 0) {
133-
npy_promotion_state = NPY_USE_LEGACY_PROMOTION;
146+
new_promotion_state = NPY_USE_LEGACY_PROMOTION;
134147
}
135148
else {
136149
PyErr_Format(PyExc_TypeError,
137150
"_set_promotion_state() argument or NPY_PROMOTION_STATE must be "
138151
"'weak', 'legacy', or 'weak_and_warn' but got '%.100S'", arg);
139152
return NULL;
140153
}
154+
set_npy_promotion_state(new_promotion_state);
141155
Py_RETURN_NONE;
142156
}
143157

@@ -930,7 +944,7 @@ PyArray_CanCastArrayTo(PyArrayObject *arr, PyArray_Descr *to,
930944
to = NULL;
931945
}
932946

933-
if (npy_promotion_state == NPY_USE_LEGACY_PROMOTION) {
947+
if (get_npy_promotion_state() == NPY_USE_LEGACY_PROMOTION) {
934948
/*
935949
* If it's a scalar, check the value. (This only currently matters for
936950
* numeric types and for `to == NULL` it can't be numeric.)
@@ -1954,10 +1968,11 @@ PyArray_CheckLegacyResultType(
19541968
npy_intp ndtypes, PyArray_Descr **dtypes)
19551969
{
19561970
PyArray_Descr *ret = NULL;
1957-
if (npy_promotion_state == NPY_USE_WEAK_PROMOTION) {
1971+
int promotion_state = get_npy_promotion_state();
1972+
if (promotion_state == NPY_USE_WEAK_PROMOTION) {
19581973
return 0;
19591974
}
1960-
if (npy_promotion_state == NPY_USE_WEAK_PROMOTION_AND_WARN
1975+
if (promotion_state == NPY_USE_WEAK_PROMOTION_AND_WARN
19611976
&& !npy_give_promotion_warnings()) {
19621977
return 0;
19631978
}
@@ -2054,12 +2069,13 @@ PyArray_CheckLegacyResultType(
20542069
Py_DECREF(ret);
20552070
return 0;
20562071
}
2057-
if (npy_promotion_state == NPY_USE_LEGACY_PROMOTION) {
2072+
2073+
if (promotion_state == NPY_USE_LEGACY_PROMOTION) {
20582074
Py_SETREF(*new_result, ret);
20592075
return 0;
20602076
}
20612077

2062-
assert(npy_promotion_state == NPY_USE_WEAK_PROMOTION_AND_WARN);
2078+
assert(promotion_state == NPY_USE_WEAK_PROMOTION_AND_WARN);
20632079
if (PyErr_WarnFormat(PyExc_UserWarning, 1,
20642080
"result dtype changed due to the removal of value-based "
20652081
"promotion from NumPy. Changed from %S to %S.",

numpy/_core/src/multiarray/convert_datatype.h

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ extern NPY_NO_EXPORT npy_intp REQUIRED_STR_LEN[];
1212
#define NPY_USE_LEGACY_PROMOTION 0
1313
#define NPY_USE_WEAK_PROMOTION 1
1414
#define NPY_USE_WEAK_PROMOTION_AND_WARN 2
15-
extern NPY_NO_EXPORT int npy_promotion_state;
15+
1616
extern NPY_NO_EXPORT PyObject *NO_NEP50_WARNING_CTX;
1717
extern NPY_NO_EXPORT PyObject *npy_DTypePromotionError;
1818
extern NPY_NO_EXPORT PyObject *npy_UFuncNoLoopError;
@@ -137,6 +137,12 @@ simple_cast_resolve_descriptors(
137137
NPY_NO_EXPORT int
138138
PyArray_InitializeCasts(void);
139139

140+
NPY_NO_EXPORT int
141+
get_npy_promotion_state();
142+
143+
NPY_NO_EXPORT void
144+
set_npy_promotion_state(int new_promotion_state);
145+
140146
#ifdef __cplusplus
141147
}
142148
#endif

numpy/_core/src/multiarray/multiarraymodule.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3493,7 +3493,7 @@ array_can_cast_safely(PyObject *NPY_UNUSED(self),
34933493
* TODO: `PyArray_IsScalar` should not be required for new dtypes.
34943494
* weak-promotion branch is in practice identical to dtype one.
34953495
*/
3496-
if (npy_promotion_state == NPY_USE_WEAK_PROMOTION) {
3496+
if (get_npy_promotion_state() == NPY_USE_WEAK_PROMOTION) {
34973497
PyObject *descr = PyObject_GetAttr(from_obj, npy_ma_str_dtype);
34983498
if (descr == NULL) {
34993499
goto finish;

numpy/_core/src/umath/dispatching.c

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -966,8 +966,10 @@ promote_and_get_ufuncimpl(PyUFuncObject *ufunc,
966966
}
967967
}
968968

969+
int current_promotion_state = get_npy_promotion_state();
970+
969971
if (force_legacy_promotion
970-
&& npy_promotion_state == NPY_USE_LEGACY_PROMOTION
972+
&& current_promotion_state == NPY_USE_LEGACY_PROMOTION
971973
&& (ufunc->ntypes != 0 || ufunc->userloops != NULL)) {
972974
/*
973975
* We must use legacy promotion for value-based logic. Call the old
@@ -982,11 +984,10 @@ promote_and_get_ufuncimpl(PyUFuncObject *ufunc,
982984
}
983985

984986
/* Pause warnings and always use "new" path */
985-
int old_promotion_state = npy_promotion_state;
986-
npy_promotion_state = NPY_USE_WEAK_PROMOTION;
987+
set_npy_promotion_state(NPY_USE_WEAK_PROMOTION);
987988
PyObject *info = promote_and_get_info_and_ufuncimpl(ufunc,
988989
ops, signature, op_dtypes, allow_legacy_promotion);
989-
npy_promotion_state = old_promotion_state;
990+
set_npy_promotion_state(current_promotion_state);
990991

991992
if (info == NULL) {
992993
goto handle_error;
@@ -996,7 +997,7 @@ promote_and_get_ufuncimpl(PyUFuncObject *ufunc,
996997
PyObject *all_dtypes = PyTuple_GET_ITEM(info, 0);
997998

998999
/* If necessary, check if the old result would have been different */
999-
if (NPY_UNLIKELY(npy_promotion_state == NPY_USE_WEAK_PROMOTION_AND_WARN)
1000+
if (NPY_UNLIKELY(current_promotion_state == NPY_USE_WEAK_PROMOTION_AND_WARN)
10001001
&& (force_legacy_promotion || promoting_pyscalars)
10011002
&& npy_give_promotion_warnings()) {
10021003
PyArray_DTypeMeta *check_dtypes[NPY_MAXARGS];
@@ -1005,11 +1006,11 @@ promote_and_get_ufuncimpl(PyUFuncObject *ufunc,
10051006
all_dtypes, i);
10061007
}
10071008
/* Before calling to the legacy promotion, pretend that is the state: */
1008-
npy_promotion_state = NPY_USE_LEGACY_PROMOTION;
1009+
set_npy_promotion_state(NPY_USE_LEGACY_PROMOTION);
10091010
int res = legacy_promote_using_legacy_type_resolver(ufunc,
10101011
ops, signature, check_dtypes, NULL, NPY_TRUE);
10111012
/* Reset the promotion state: */
1012-
npy_promotion_state = NPY_USE_WEAK_PROMOTION_AND_WARN;
1013+
set_npy_promotion_state(NPY_USE_WEAK_PROMOTION_AND_WARN);
10131014
if (res < 0) {
10141015
goto handle_error;
10151016
}

numpy/_core/src/umath/scalarmath.c.src

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -963,7 +963,7 @@ convert_to_@name@(PyObject *value, @type@ *result, npy_bool *may_need_deferring)
963963
*may_need_deferring = NPY_TRUE;
964964
}
965965
if (!IS_SAFE(NPY_DOUBLE, NPY_@TYPE@)) {
966-
if (npy_promotion_state != NPY_USE_WEAK_PROMOTION) {
966+
if (get_npy_promotion_state() != NPY_USE_WEAK_PROMOTION) {
967967
/* Legacy promotion and weak-and-warn not handled here */
968968
return PROMOTION_REQUIRED;
969969
}
@@ -986,7 +986,7 @@ convert_to_@name@(PyObject *value, @type@ *result, npy_bool *may_need_deferring)
986986
* long -> (c)longdouble is safe, so `OTHER_IS_UNKNOWN_OBJECT` will
987987
* be returned below for huge integers.
988988
*/
989-
if (npy_promotion_state != NPY_USE_WEAK_PROMOTION) {
989+
if (get_npy_promotion_state() != NPY_USE_WEAK_PROMOTION) {
990990
/* Legacy promotion and weak-and-warn not handled here */
991991
return PROMOTION_REQUIRED;
992992
}
@@ -996,7 +996,7 @@ convert_to_@name@(PyObject *value, @type@ *result, npy_bool *may_need_deferring)
996996
long val = PyLong_AsLongAndOverflow(value, &overflow);
997997
if (overflow) {
998998
/* handle as if "unsafe" */
999-
if (npy_promotion_state != NPY_USE_WEAK_PROMOTION) {
999+
if (get_npy_promotion_state() != NPY_USE_WEAK_PROMOTION) {
10001000
return OTHER_IS_UNKNOWN_OBJECT;
10011001
}
10021002
return CONVERT_PYSCALAR;
@@ -1018,7 +1018,7 @@ convert_to_@name@(PyObject *value, @type@ *result, npy_bool *may_need_deferring)
10181018
*may_need_deferring = NPY_TRUE;
10191019
}
10201020
if (!IS_SAFE(NPY_CDOUBLE, NPY_@TYPE@)) {
1021-
if (npy_promotion_state != NPY_USE_WEAK_PROMOTION) {
1021+
if (get_npy_promotion_state() != NPY_USE_WEAK_PROMOTION) {
10221022
/* Legacy promotion and weak-and-warn not handled here */
10231023
return PROMOTION_REQUIRED;
10241024
}

numpy/_core/src/umath/ufunc_object.c

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -670,7 +670,7 @@ convert_ufunc_arguments(PyUFuncObject *ufunc,
670670

671671
// TODO: Is this equivalent/better by removing the logic which enforces
672672
// that we always use weak promotion in the core?
673-
if (npy_promotion_state == NPY_USE_LEGACY_PROMOTION) {
673+
if (get_npy_promotion_state() == NPY_USE_LEGACY_PROMOTION) {
674674
continue; /* Skip use of special dtypes */
675675
}
676676

@@ -6073,8 +6073,8 @@ py_resolve_dtypes_generic(PyUFuncObject *ufunc, npy_bool return_context,
60736073
PyArray_Descr *operation_descrs[NPY_MAXARGS] = {NULL};
60746074

60756075
/* This entry-point to promotion lives in the NEP 50 future: */
6076-
int original_promotion_state = npy_promotion_state;
6077-
npy_promotion_state = NPY_USE_WEAK_PROMOTION;
6076+
int original_promotion_state = get_npy_promotion_state();
6077+
set_npy_promotion_state(NPY_USE_WEAK_PROMOTION);
60786078

60796079
npy_bool promoting_pyscalars = NPY_FALSE;
60806080
npy_bool allow_legacy_promotion = NPY_TRUE;
@@ -6261,7 +6261,7 @@ py_resolve_dtypes_generic(PyUFuncObject *ufunc, npy_bool return_context,
62616261
Py_DECREF(capsule);
62626262

62636263
finish:
6264-
npy_promotion_state = original_promotion_state;
6264+
set_npy_promotion_state(original_promotion_state);
62656265

62666266
Py_XDECREF(result_dtype_tuple);
62676267
for (int i = 0; i < ufunc->nargs; i++) {

0 commit comments

Comments
 (0)