bpo-45412: Remove Py_SET_ERRNO_ON_MATH_ERROR() macro (GH-28820)

Remove the following math macros using the errno variable:

* Py_ADJUST_ERANGE1()
* Py_ADJUST_ERANGE2()
* Py_OVERFLOWED()
* Py_SET_ERANGE_IF_OVERFLOW()
* Py_SET_ERRNO_ON_MATH_ERROR()

Create pycore_pymath.h internal header file.

Rename Py_ADJUST_ERANGE1() and Py_ADJUST_ERANGE2() to
_Py_ADJUST_ERANGE1() and _Py_ADJUST_ERANGE2(), and convert these
macros to static inline functions.

Move the following macros to pycore_pymath.h:

* _Py_IntegralTypeSigned()
* _Py_IntegralTypeMax()
* _Py_IntegralTypeMin()
* _Py_InIntegralTypeRange()
This commit is contained in:
Victor Stinner 2021-10-11 21:00:25 +02:00 committed by GitHub
parent 659812b451
commit 2f92e2a590
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 99 additions and 113 deletions

View File

@ -577,3 +577,13 @@ Removed
Use the new :c:type:`PyConfig` API of the :ref:`Python Initialization Configuration
<init-config>` instead (:pep:`587`).
(Contributed by Victor Stinner in :issue:`44113`.)
* Remove the following math macros using the ``errno`` variable:
* ``Py_ADJUST_ERANGE1()``
* ``Py_ADJUST_ERANGE2()``
* ``Py_OVERFLOWED()``
* ``Py_SET_ERANGE_IF_OVERFLOW()``
* ``Py_SET_ERRNO_ON_MATH_ERROR()``
(Contributed by Victor Stinner in :issue:`45412`.)

View File

@ -0,0 +1,73 @@
#ifndef Py_INTERNAL_PYMATH_H
#define Py_INTERNAL_PYMATH_H
#ifdef __cplusplus
extern "C" {
#endif
#ifndef Py_BUILD_CORE
# error "this header requires Py_BUILD_CORE define"
#endif
/* _Py_ADJUST_ERANGE1(x)
* _Py_ADJUST_ERANGE2(x, y)
* Set errno to 0 before calling a libm function, and invoke one of these
* macros after, passing the function result(s) (_Py_ADJUST_ERANGE2 is useful
* for functions returning complex results). This makes two kinds of
* adjustments to errno: (A) If it looks like the platform libm set
* errno=ERANGE due to underflow, clear errno. (B) If it looks like the
* platform libm overflowed but didn't set errno, force errno to ERANGE. In
* effect, we're trying to force a useful implementation of C89 errno
* behavior.
* Caution:
* This isn't reliable. See Py_OVERFLOWED comments.
* X and Y may be evaluated more than once.
*/
static inline void _Py_ADJUST_ERANGE1(double x)
{
if (errno == 0) {
if (x == Py_HUGE_VAL || x == -Py_HUGE_VAL) {
errno = ERANGE;
}
}
else if (errno == ERANGE && x == 0.0) {
errno = 0;
}
}
static inline void _Py_ADJUST_ERANGE2(double x, double y)
{
if (x == Py_HUGE_VAL || x == -Py_HUGE_VAL ||
y == Py_HUGE_VAL || y == -Py_HUGE_VAL)
{
if (errno == 0) {
errno = ERANGE;
}
}
else if (errno == ERANGE) {
errno = 0;
}
}
// Return whether integral type *type* is signed or not.
#define _Py_IntegralTypeSigned(type) \
((type)(-1) < 0)
// Return the maximum value of integral type *type*.
#define _Py_IntegralTypeMax(type) \
((_Py_IntegralTypeSigned(type)) ? (((((type)1 << (sizeof(type)*CHAR_BIT - 2)) - 1) << 1) + 1) : ~(type)0)
// Return the minimum value of integral type *type*.
#define _Py_IntegralTypeMin(type) \
((_Py_IntegralTypeSigned(type)) ? -_Py_IntegralTypeMax(type) - 1 : 0)
// Check whether *v* is in the range of integral type *type*. This is most
// useful if *v* is floating-point, since demoting a floating-point *v* to an
// integral type that cannot represent *v*'s integral part is undefined
// behavior.
#define _Py_InIntegralTypeRange(type, v) \
(_Py_IntegralTypeMin(type) <= v && v <= _Py_IntegralTypeMax(type))
#ifdef __cplusplus
}
#endif
#endif /* !Py_INTERNAL_PYMATH_H */

View File

@ -176,50 +176,4 @@ PyAPI_FUNC(void) _Py_set_387controlword(unsigned short);
#endif /* __INTEL_COMPILER */
#endif
/* Py_OVERFLOWED(X)
* Return 1 iff a libm function overflowed. Set errno to 0 before calling
* a libm function, and invoke this macro after, passing the function
* result.
* Caution:
* This isn't reliable. C99 no longer requires libm to set errno under
* any exceptional condition, but does require +- HUGE_VAL return
* values on overflow. A 754 box *probably* maps HUGE_VAL to a
* double infinity, and we're cool if that's so, unless the input
* was an infinity and an infinity is the expected result. A C89
* system sets errno to ERANGE, so we check for that too. We're
* out of luck if a C99 754 box doesn't map HUGE_VAL to +Inf, or
* if the returned result is a NaN, or if a C89 box returns HUGE_VAL
* in non-overflow cases.
* X is evaluated more than once.
* Some platforms have better way to spell this, so expect some #ifdef'ery.
*
* OpenBSD uses 'isinf()' because a compiler bug on that platform causes
* the longer macro version to be mis-compiled. This isn't optimal, and
* should be removed once a newer compiler is available on that platform.
* The system that had the failure was running OpenBSD 3.2 on Intel, with
* gcc 2.95.3.
*
* According to Tim's checkin, the FreeBSD systems use isinf() to work
* around a FPE bug on that platform.
*/
#if defined(__FreeBSD__) || defined(__OpenBSD__)
#define Py_OVERFLOWED(X) isinf(X)
#else
#define Py_OVERFLOWED(X) ((X) != 0.0 && (errno == ERANGE || \
(X) == Py_HUGE_VAL || \
(X) == -Py_HUGE_VAL))
#endif
/* Return whether integral type *type* is signed or not. */
#define _Py_IntegralTypeSigned(type) ((type)(-1) < 0)
/* Return the maximum value of integral type *type*. */
#define _Py_IntegralTypeMax(type) ((_Py_IntegralTypeSigned(type)) ? (((((type)1 << (sizeof(type)*CHAR_BIT - 2)) - 1) << 1) + 1) : ~(type)0)
/* Return the minimum value of integral type *type*. */
#define _Py_IntegralTypeMin(type) ((_Py_IntegralTypeSigned(type)) ? -_Py_IntegralTypeMax(type) - 1 : 0)
/* Check whether *v* is in the range of integral type *type*. This is most
* useful if *v* is floating-point, since demoting a floating-point *v* to an
* integral type that cannot represent *v*'s integral part is undefined
* behavior. */
#define _Py_InIntegralTypeRange(type, v) (_Py_IntegralTypeMin(type) <= v && v <= _Py_IntegralTypeMax(type))
#endif /* Py_PYMATH_H */

View File

@ -316,69 +316,6 @@ extern "C" {
#define Py_SAFE_DOWNCAST(VALUE, WIDE, NARROW) (NARROW)(VALUE)
#endif
/* Py_SET_ERRNO_ON_MATH_ERROR(x)
* If a libm function did not set errno, but it looks like the result
* overflowed or not-a-number, set errno to ERANGE or EDOM. Set errno
* to 0 before calling a libm function, and invoke this macro after,
* passing the function result.
* Caution:
* This isn't reliable. See Py_OVERFLOWED comments.
* X is evaluated more than once.
*/
#if defined(__FreeBSD__) || defined(__OpenBSD__) || (defined(__hpux) && defined(__ia64))
#define _Py_SET_EDOM_FOR_NAN(X) if (isnan(X)) errno = EDOM;
#else
#define _Py_SET_EDOM_FOR_NAN(X) ;
#endif
#define Py_SET_ERRNO_ON_MATH_ERROR(X) \
do { \
if (errno == 0) { \
if ((X) == Py_HUGE_VAL || (X) == -Py_HUGE_VAL) \
errno = ERANGE; \
else _Py_SET_EDOM_FOR_NAN(X) \
} \
} while(0)
/* Py_SET_ERANGE_IF_OVERFLOW(x)
* An alias of Py_SET_ERRNO_ON_MATH_ERROR for backward-compatibility.
*/
#define Py_SET_ERANGE_IF_OVERFLOW(X) Py_SET_ERRNO_ON_MATH_ERROR(X)
/* Py_ADJUST_ERANGE1(x)
* Py_ADJUST_ERANGE2(x, y)
* Set errno to 0 before calling a libm function, and invoke one of these
* macros after, passing the function result(s) (Py_ADJUST_ERANGE2 is useful
* for functions returning complex results). This makes two kinds of
* adjustments to errno: (A) If it looks like the platform libm set
* errno=ERANGE due to underflow, clear errno. (B) If it looks like the
* platform libm overflowed but didn't set errno, force errno to ERANGE. In
* effect, we're trying to force a useful implementation of C89 errno
* behavior.
* Caution:
* This isn't reliable. See Py_OVERFLOWED comments.
* X and Y may be evaluated more than once.
*/
#define Py_ADJUST_ERANGE1(X) \
do { \
if (errno == 0) { \
if ((X) == Py_HUGE_VAL || (X) == -Py_HUGE_VAL) \
errno = ERANGE; \
} \
else if (errno == ERANGE && (X) == 0.0) \
errno = 0; \
} while(0)
#define Py_ADJUST_ERANGE2(X, Y) \
do { \
if ((X) == Py_HUGE_VAL || (X) == -Py_HUGE_VAL || \
(Y) == Py_HUGE_VAL || (Y) == -Py_HUGE_VAL) { \
if (errno == 0) \
errno = ERANGE; \
} \
else if (errno == ERANGE) \
errno = 0; \
} while(0)
/* The functions _Py_dg_strtod and _Py_dg_dtoa in Python/dtoa.c (which are
* required to support the short float repr introduced in Python 3.1) require
* that the floating-point unit that's being used for arithmetic operations

View File

@ -0,0 +1,9 @@
Remove the following math macros using the ``errno`` variable:
* ``Py_ADJUST_ERANGE1()``
* ``Py_ADJUST_ERANGE2()``
* ``Py_OVERFLOWED()``
* ``Py_SET_ERANGE_IF_OVERFLOW()``
* ``Py_SET_ERRNO_ON_MATH_ERROR()``
Patch by Victor Stinner.

View File

@ -8,6 +8,7 @@
#include "Python.h"
#include "pycore_long.h" // _PyLong_GetZero()
#include "pycore_object.h" // _PyObject_Init()
#include "pycore_pymath.h" // _Py_ADJUST_ERANGE2()
#include "structmember.h" // PyMemberDef
@ -525,7 +526,7 @@ complex_pow(PyObject *v, PyObject *w, PyObject *z)
p = _Py_c_pow(a, b);
}
Py_ADJUST_ERANGE2(p.real, p.imag);
_Py_ADJUST_ERANGE2(p.real, p.imag);
if (errno == EDOM) {
PyErr_SetString(PyExc_ZeroDivisionError,
"0.0 to a negative or complex power");

View File

@ -8,6 +8,7 @@
#include "pycore_interp.h" // _PyInterpreterState.float_state
#include "pycore_long.h" // _PyLong_GetOne()
#include "pycore_object.h" // _PyObject_Init()
#include "pycore_pymath.h" // _Py_ADJUST_ERANGE1()
#include "pycore_pystate.h" // _PyInterpreterState_GET()
#include <ctype.h>
@ -809,7 +810,7 @@ float_pow(PyObject *v, PyObject *w, PyObject *z)
*/
errno = 0;
ix = pow(iv, iw);
Py_ADJUST_ERANGE1(ix);
_Py_ADJUST_ERANGE1(ix);
if (negate_result)
ix = -ix;

View File

@ -1,10 +1,11 @@
#include "Python.h"
#include "pycore_pymath.h" // _Py_InIntegralTypeRange()
#ifdef MS_WINDOWS
#include <winsock2.h> /* struct timeval */
# include <winsock2.h> // struct timeval
#endif
#if defined(__APPLE__)
#include <mach/mach_time.h> /* mach_absolute_time(), mach_timebase_info() */
# include <mach/mach_time.h> // mach_absolute_time(), mach_timebase_info()
#if defined(__APPLE__) && defined(__has_builtin)
# if __has_builtin(__builtin_available)