Skip to content

Commit e2ca4dc

Browse files
authored
gh-157495: Implement Py_MIN/MAX/ABS using typeof() (#157496)
On GCC and clang, implement Py_MIN(), Py_MAX() and Py_ABS() using typeof() and statement expression to only evaluate each argument once and detect signed/unsigned comparison. Test also these macros in test_cext and test_cppext to test different compiler flags and test the limited C API.
1 parent 82952e3 commit e2ca4dc

8 files changed

Lines changed: 62 additions & 13 deletions

File tree

Include/pymacro.h

Lines changed: 32 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -107,15 +107,40 @@
107107
# endif
108108
#endif
109109

110+
#if ((defined(__GNUC__) || defined(__clang__)) \
111+
&& defined(_Py_TYPEOF) && !defined(__cplusplus))
112+
// Implement Py_MIN(), Py_MAX() and Py_ABS() using _Py_TYPEOF() and
113+
// statement expression to only evaluate each argument only once.
114+
// It cannot be used in C++: ISO C++ forbids braced-groups within
115+
// expressions. Statement expression is a GNU extension. Use __extension__
116+
// to avoid compiler warning in pedantic mode.
110117

111-
/* Minimum value between x and y */
112-
#define Py_MIN(x, y) (((x) > (y)) ? (y) : (x))
113-
114-
/* Maximum value between x and y */
115-
#define Py_MAX(x, y) (((x) > (y)) ? (x) : (y))
118+
/* Minimum value between x and y */
119+
# define Py_MIN(x, y) \
120+
__extension__ \
121+
({ _Py_TYPEOF (x) _x = (x); \
122+
_Py_TYPEOF (y) _y = (y); \
123+
_x < _y ? _x : _y; })
124+
/* Maximum value between x and y */
125+
# define Py_MAX(x, y) \
126+
__extension__ \
127+
({ _Py_TYPEOF (x) _x = (x); \
128+
_Py_TYPEOF (y) _y = (y); \
129+
_x > _y ? _x : _y; })
130+
/* Absolute value of the number x */
131+
# define Py_ABS(x) \
132+
__extension__ \
133+
({ _Py_TYPEOF (x) _x = (x); \
134+
_x < 0 ? -_x : _x; })
135+
#else
136+
/* Minimum value between x and y */
137+
# define Py_MIN(x, y) (((x) > (y)) ? (y) : (x))
138+
/* Maximum value between x and y */
139+
# define Py_MAX(x, y) (((x) > (y)) ? (x) : (y))
140+
/* Absolute value of the number x */
141+
# define Py_ABS(x) ((x) < 0 ? -(x) : (x))
142+
#endif
116143

117-
/* Absolute value of the number x */
118-
#define Py_ABS(x) ((x) < 0 ? -(x) : (x))
119144
/* Safer implementation that avoids an undefined behavior for the minimal
120145
value of the signed integer type if its absolute value is larger than
121146
the maximal value of the signed integer type (in the two's complement

Lib/test/test_cext/extension.c

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,10 +91,15 @@ _testcext_exec(PyObject *module)
9191
if (!result) return -1;
9292
Py_DECREF(result);
9393

94-
// test Py_BUILD_ASSERT() and Py_BUILD_ASSERT_EXPR()
94+
// Test Py_BUILD_ASSERT() and Py_BUILD_ASSERT_EXPR()
9595
Py_BUILD_ASSERT(sizeof(int) == sizeof(unsigned int));
9696
assert(Py_BUILD_ASSERT_EXPR(sizeof(int) == sizeof(unsigned int)) == 0);
9797

98+
// Test Py_MIN(), Py_MAX(), Py_ABS()
99+
assert(Py_MIN(5, 11) == 5);
100+
assert(Py_MAX(5, 11) == 11);
101+
assert(Py_ABS(-5) == 5);
102+
98103
// Test Py_CLEAR(): use typeof()/__typeof__() if available, or memcpy()
99104
obj = Py_None;
100105
Py_CLEAR(obj);

Lib/test/test_cppext/extension.cpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -302,6 +302,11 @@ _testcppext_exec(PyObject *module)
302302
Py_BUILD_ASSERT(sizeof(int) == sizeof(unsigned int));
303303
assert(Py_BUILD_ASSERT_EXPR(sizeof(int) == sizeof(unsigned int)) == 0);
304304

305+
// Test Py_MIN(), Py_MAX(), Py_ABS()
306+
assert(Py_MIN(5, 11) == 5);
307+
assert(Py_MAX(5, 11) == 11);
308+
assert(Py_ABS(-5) == 5);
309+
305310
// Test Py_CLEAR(): use typeof()/__typeof__() if available, or memcpy()
306311
PyObject *obj = Py_None;
307312
Py_CLEAR(obj);
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
If ``typeof()`` is available, implement :c:macro:`Py_MIN`, :c:macro:`Py_MAX`
2+
and :c:macro:`Py_ABS` using ``typeof()`` and statement expression to only
3+
evaluate each argument once. Patch by Victor Stinner.

Modules/_ssl.c

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5883,7 +5883,8 @@ _ssl_MemoryBIO_read_impl(PySSLMemoryBIO *self, int len)
58835883
{
58845884
int avail, nbytes;
58855885

5886-
avail = (int)Py_MIN(BIO_ctrl_pending(self->bio), INT_MAX);
5886+
size_t pending = BIO_ctrl_pending(self->bio);
5887+
avail = (int)Py_MIN(pending, (size_t)INT_MAX);
58875888
if ((len < 0) || (len > avail))
58885889
len = avail;
58895890

Modules/_testcapimodule.c

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2045,12 +2045,22 @@ test_macros(PyObject *self, PyObject *Py_UNUSED(args))
20452045
static_assert(1 == 1, "bug");
20462046
Py_BUILD_ASSERT(1 == 1);
20472047

2048-
20492048
// Py_MIN(), Py_MAX(), Py_ABS()
20502049
assert(Py_MIN(5, 11) == 5);
20512050
assert(Py_MAX(5, 11) == 11);
20522051
assert(Py_ABS(-5) == 5);
20532052

2053+
#if ((defined(__GNUC__) || defined(__clang__)) \
2054+
&& defined(_Py_TYPEOF) && !defined(__cplusplus))
2055+
// When _Py_TYPEOF() is available, arguments are only evaluated once
2056+
int x = 5, y = 11;
2057+
assert(Py_MIN(++x, ++y) == 6);
2058+
x = 5; y = 11;
2059+
assert(Py_MAX(++x, ++y) == 12);
2060+
x = -5;
2061+
assert(Py_ABS(--x) == 6);
2062+
#endif
2063+
20542064
// Py_STRINGIFY()
20552065
assert(strcmp(Py_STRINGIFY(123), "123") == 0);
20562066

Modules/cjkcodecs/cjkcodecs.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -163,7 +163,7 @@ get_module_state(PyObject *mod)
163163
do { \
164164
Py_UCS4 _c1 = (c1); \
165165
Py_UCS4 _c2 = (c2); \
166-
if (_PyUnicodeWriter_Prepare(writer, 2, Py_MAX(_c1, c2)) < 0) \
166+
if (_PyUnicodeWriter_Prepare(writer, 2, Py_MAX(_c1, _c2)) < 0) \
167167
return MBERR_EXCEPTION; \
168168
PyUnicode_WRITE(writer->kind, writer->data, writer->pos, _c1); \
169169
PyUnicode_WRITE(writer->kind, writer->data, writer->pos + 1, _c2); \

Objects/obmalloc.c

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3255,10 +3255,10 @@ _PyMem_DebugRawRealloc(void *ctx, void *p, size_t nbytes)
32553255
}
32563256
else {
32573257
size_t i = original_nbytes - ERASED_SIZE;
3258-
memcpy(data, save, Py_MIN(nbytes, ERASED_SIZE));
3258+
memcpy(data, save, Py_MIN(nbytes, (size_t)ERASED_SIZE));
32593259
if (nbytes > i) {
32603260
memcpy(data + i, &save[ERASED_SIZE],
3261-
Py_MIN(nbytes - i, ERASED_SIZE));
3261+
Py_MIN(nbytes - i, (size_t)ERASED_SIZE));
32623262
}
32633263
}
32643264
#endif

0 commit comments

Comments
 (0)