From 21764e43ef30e4b9d45ed506deff92f0739456e5 Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Mon, 14 Sep 2026 15:45:19 +0200 Subject: [PATCH 1/5] gh-157495: Implement Py_MIN() and Py_MAX() using typeof() If typeof() is available, implement Py_MIN(), Py_MAX() and Py_ABS() using typeof() and statement expression to only evaluate each argument once. Test also these macros in test_cext and test_cppext to test different compiler flags and test the limited C API. --- Include/pymacro.h | 34 +++++++++++++++---- Lib/test/test_cext/extension.c | 7 +++- Lib/test/test_cppext/extension.cpp | 5 +++ ...-09-14-16-10-23.gh-issue-157495.LzMLtE.rst | 3 ++ Modules/_testcapimodule.c | 11 +++++- 5 files changed, 51 insertions(+), 9 deletions(-) create mode 100644 Misc/NEWS.d/next/C_API/2026-09-14-16-10-23.gh-issue-157495.LzMLtE.rst diff --git a/Include/pymacro.h b/Include/pymacro.h index 7ecce44a0d2a428..d8737495cb7c864 100644 --- a/Include/pymacro.h +++ b/Include/pymacro.h @@ -107,15 +107,35 @@ # endif #endif +#if defined(_Py_TYPEOF) && !defined(__cplusplus) + // Implement Py_MIN(), Py_MAX() and Py_ABS() using _Py_TYPEOF() and + // statement expression to only evaluate each argument only once. + // It cannot be used in C++: ISO C++ forbids braced-groups within + // expressions. -/* Minimum value between x and y */ -#define Py_MIN(x, y) (((x) > (y)) ? (y) : (x)) - -/* Maximum value between x and y */ -#define Py_MAX(x, y) (((x) > (y)) ? (x) : (y)) + /* Minimum value between x and y */ +# define Py_MIN(x, y) \ + ({ _Py_TYPEOF (x) _x = (x); \ + _Py_TYPEOF (y) _y = (y); \ + _x < _y ? _x : _y; }) + /* Maximum value between x and y */ +# define Py_MAX(x, y) \ + ({ _Py_TYPEOF (x) _x = (x); \ + _Py_TYPEOF (y) _y = (y); \ + _x > _y ? _x : _y; }) + /* Absolute value of the number x */ +# define Py_ABS(x) \ + ({ _Py_TYPEOF (x) _x = (x); \ + _x < 0 ? -_x : _x; }) +#else + /* Minimum value between x and y */ +# define Py_MIN(x, y) (((x) > (y)) ? (y) : (x)) + /* Maximum value between x and y */ +# define Py_MAX(x, y) (((x) > (y)) ? (x) : (y)) + /* Absolute value of the number x */ +# define Py_ABS(x) ((x) < 0 ? -(x) : (x)) +#endif -/* Absolute value of the number x */ -#define Py_ABS(x) ((x) < 0 ? -(x) : (x)) /* Safer implementation that avoids an undefined behavior for the minimal value of the signed integer type if its absolute value is larger than the maximal value of the signed integer type (in the two's complement diff --git a/Lib/test/test_cext/extension.c b/Lib/test/test_cext/extension.c index 543a8096f16f8aa..9054c9dfc1ed421 100644 --- a/Lib/test/test_cext/extension.c +++ b/Lib/test/test_cext/extension.c @@ -91,10 +91,15 @@ _testcext_exec(PyObject *module) if (!result) return -1; Py_DECREF(result); - // test Py_BUILD_ASSERT() and Py_BUILD_ASSERT_EXPR() + // Test Py_BUILD_ASSERT() and Py_BUILD_ASSERT_EXPR() Py_BUILD_ASSERT(sizeof(int) == sizeof(unsigned int)); assert(Py_BUILD_ASSERT_EXPR(sizeof(int) == sizeof(unsigned int)) == 0); + // Test Py_MIN(), Py_MAX(), Py_ABS() + assert(Py_MIN(5, 11) == 5); + assert(Py_MAX(5, 11) == 11); + assert(Py_ABS(-5) == 5); + // Test Py_CLEAR(): use typeof()/__typeof__() if available, or memcpy() obj = Py_None; Py_CLEAR(obj); diff --git a/Lib/test/test_cppext/extension.cpp b/Lib/test/test_cppext/extension.cpp index 62ce81e2b510c7a..c6c131976ff453b 100644 --- a/Lib/test/test_cppext/extension.cpp +++ b/Lib/test/test_cppext/extension.cpp @@ -294,6 +294,11 @@ _testcppext_exec(PyObject *module) Py_BUILD_ASSERT(sizeof(int) == sizeof(unsigned int)); assert(Py_BUILD_ASSERT_EXPR(sizeof(int) == sizeof(unsigned int)) == 0); + // Test Py_MIN(), Py_MAX(), Py_ABS() + assert(Py_MIN(5, 11) == 5); + assert(Py_MAX(5, 11) == 11); + assert(Py_ABS(-5) == 5); + // Test Py_CLEAR(): use typeof()/__typeof__() if available, or memcpy() PyObject *obj = Py_None; Py_CLEAR(obj); diff --git a/Misc/NEWS.d/next/C_API/2026-09-14-16-10-23.gh-issue-157495.LzMLtE.rst b/Misc/NEWS.d/next/C_API/2026-09-14-16-10-23.gh-issue-157495.LzMLtE.rst new file mode 100644 index 000000000000000..0e89171bba86579 --- /dev/null +++ b/Misc/NEWS.d/next/C_API/2026-09-14-16-10-23.gh-issue-157495.LzMLtE.rst @@ -0,0 +1,3 @@ +If ``typeof()`` is available, implement :c:macro:`Py_MIN`, :c:macro:`Py_MAX` +and :c:macro:`Py_ABS` using ``typeof()`` and statement expression to only +evaluate each argument once. Patch by Victor Stinner. diff --git a/Modules/_testcapimodule.c b/Modules/_testcapimodule.c index eb769294fd21db8..7435641d570a195 100644 --- a/Modules/_testcapimodule.c +++ b/Modules/_testcapimodule.c @@ -2045,12 +2045,21 @@ test_macros(PyObject *self, PyObject *Py_UNUSED(args)) static_assert(1 == 1, "bug"); Py_BUILD_ASSERT(1 == 1); - // Py_MIN(), Py_MAX(), Py_ABS() assert(Py_MIN(5, 11) == 5); assert(Py_MAX(5, 11) == 11); assert(Py_ABS(-5) == 5); +#ifdef _Py_TYPEOF + // When _Py_TYPEOF() is available, arguments are only evaluated once + int x = 5, y = 11; + assert(Py_MIN(++x, ++y) == 6); + x = 5; y = 11; + assert(Py_MAX(++x, ++y) == 12); + x = -5; + assert(Py_ABS(--x) == 6); +#endif + // Py_STRINGIFY() assert(strcmp(Py_STRINGIFY(123), "123") == 0); From ad65f4d1fe2fbac34ae87d4fb4f4f791a42baff1 Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Mon, 14 Sep 2026 17:40:38 +0200 Subject: [PATCH 2/5] Only use statement expression on GCC and clang --- Include/pymacro.h | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Include/pymacro.h b/Include/pymacro.h index d8737495cb7c864..2a703590aa98adf 100644 --- a/Include/pymacro.h +++ b/Include/pymacro.h @@ -107,11 +107,12 @@ # endif #endif -#if defined(_Py_TYPEOF) && !defined(__cplusplus) +#if ((defined(__GNUC__) || defined(__clang__)) \ + && defined(_Py_TYPEOF) && !defined(__cplusplus)) // Implement Py_MIN(), Py_MAX() and Py_ABS() using _Py_TYPEOF() and // statement expression to only evaluate each argument only once. // It cannot be used in C++: ISO C++ forbids braced-groups within - // expressions. + // expressions. Statement expression is a GNU extension. /* Minimum value between x and y */ # define Py_MIN(x, y) \ From e28e5069caf86ee4108fb5cbfcb4e5e228b88106 Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Mon, 14 Sep 2026 17:49:59 +0200 Subject: [PATCH 3/5] Fix signed/unsigned comparisons --- Modules/_ssl.c | 3 ++- Modules/cjkcodecs/cjkcodecs.h | 2 +- Objects/obmalloc.c | 4 ++-- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/Modules/_ssl.c b/Modules/_ssl.c index 73b32c1d86c72ec..9bddb9ce62d5b99 100644 --- a/Modules/_ssl.c +++ b/Modules/_ssl.c @@ -5883,7 +5883,8 @@ _ssl_MemoryBIO_read_impl(PySSLMemoryBIO *self, int len) { int avail, nbytes; - avail = (int)Py_MIN(BIO_ctrl_pending(self->bio), INT_MAX); + size_t pending = BIO_ctrl_pending(self->bio); + avail = (int)Py_MIN(pending, (size_t)INT_MAX); if ((len < 0) || (len > avail)) len = avail; diff --git a/Modules/cjkcodecs/cjkcodecs.h b/Modules/cjkcodecs/cjkcodecs.h index 41e1287c8650701..b4f316ce0c06e46 100644 --- a/Modules/cjkcodecs/cjkcodecs.h +++ b/Modules/cjkcodecs/cjkcodecs.h @@ -163,7 +163,7 @@ get_module_state(PyObject *mod) do { \ Py_UCS4 _c1 = (c1); \ Py_UCS4 _c2 = (c2); \ - if (_PyUnicodeWriter_Prepare(writer, 2, Py_MAX(_c1, c2)) < 0) \ + if (_PyUnicodeWriter_Prepare(writer, 2, Py_MAX(_c1, _c2)) < 0) \ return MBERR_EXCEPTION; \ PyUnicode_WRITE(writer->kind, writer->data, writer->pos, _c1); \ PyUnicode_WRITE(writer->kind, writer->data, writer->pos + 1, _c2); \ diff --git a/Objects/obmalloc.c b/Objects/obmalloc.c index 0947d47c8a55582..9a41a4224672233 100644 --- a/Objects/obmalloc.c +++ b/Objects/obmalloc.c @@ -3255,10 +3255,10 @@ _PyMem_DebugRawRealloc(void *ctx, void *p, size_t nbytes) } else { size_t i = original_nbytes - ERASED_SIZE; - memcpy(data, save, Py_MIN(nbytes, ERASED_SIZE)); + memcpy(data, save, Py_MIN(nbytes, (size_t)ERASED_SIZE)); if (nbytes > i) { memcpy(data + i, &save[ERASED_SIZE], - Py_MIN(nbytes - i, ERASED_SIZE)); + Py_MIN(nbytes - i, (size_t)ERASED_SIZE)); } } #endif From be8754dbc4ab1247cdf1db470dd84ef239b2ab9b Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Mon, 14 Sep 2026 17:57:11 +0200 Subject: [PATCH 4/5] Add __extension__ --- Include/pymacro.h | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Include/pymacro.h b/Include/pymacro.h index 2a703590aa98adf..f31c9505a6a812b 100644 --- a/Include/pymacro.h +++ b/Include/pymacro.h @@ -112,20 +112,24 @@ // Implement Py_MIN(), Py_MAX() and Py_ABS() using _Py_TYPEOF() and // statement expression to only evaluate each argument only once. // It cannot be used in C++: ISO C++ forbids braced-groups within - // expressions. Statement expression is a GNU extension. + // expressions. Statement expression is a GNU extension. Use __extension__ + // to avoid compiler warning in pedantic mode. /* Minimum value between x and y */ # define Py_MIN(x, y) \ + __extension__ \ ({ _Py_TYPEOF (x) _x = (x); \ _Py_TYPEOF (y) _y = (y); \ _x < _y ? _x : _y; }) /* Maximum value between x and y */ # define Py_MAX(x, y) \ + __extension__ \ ({ _Py_TYPEOF (x) _x = (x); \ _Py_TYPEOF (y) _y = (y); \ _x > _y ? _x : _y; }) /* Absolute value of the number x */ # define Py_ABS(x) \ + __extension__ \ ({ _Py_TYPEOF (x) _x = (x); \ _x < 0 ? -_x : _x; }) #else From 9699d461d81a6c9aeef77b8c4570e4fb688fddbe Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Mon, 14 Sep 2026 18:36:43 +0200 Subject: [PATCH 5/5] Update _testcapi test for Windows --- Modules/_testcapimodule.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Modules/_testcapimodule.c b/Modules/_testcapimodule.c index 7435641d570a195..577bb14df134059 100644 --- a/Modules/_testcapimodule.c +++ b/Modules/_testcapimodule.c @@ -2050,7 +2050,8 @@ test_macros(PyObject *self, PyObject *Py_UNUSED(args)) assert(Py_MAX(5, 11) == 11); assert(Py_ABS(-5) == 5); -#ifdef _Py_TYPEOF +#if ((defined(__GNUC__) || defined(__clang__)) \ + && defined(_Py_TYPEOF) && !defined(__cplusplus)) // When _Py_TYPEOF() is available, arguments are only evaluated once int x = 5, y = 11; assert(Py_MIN(++x, ++y) == 6);