From 37bddee3f684bfed80a5fddda6d657676c7d4c7c Mon Sep 17 00:00:00 2001 From: chris-eibl <138194463+chris-eibl@users.noreply.github.com> Date: Sat, 25 Jan 2025 19:45:01 +0100 Subject: [PATCH 1/9] gh-129149: add macro PYLONG_FROM_INT Use it in PyLong_FromLong() and PyLong_FromLongLong(). This is just a refactoring and will create the same binary code. --- Objects/longobject.c | 106 ++++++++++++++++--------------------------- 1 file changed, 38 insertions(+), 68 deletions(-) diff --git a/Objects/longobject.c b/Objects/longobject.c index 905c4695f60d4f..db08a4bae5f6c6 100644 --- a/Objects/longobject.c +++ b/Objects/longobject.c @@ -332,45 +332,48 @@ _PyLong_Negate(PyLongObject **x_p) Py_DECREF(x); } +#define PYLONG_FROM_INT(INT_TYPE, ival) \ + do { \ + PyLongObject *v; \ + unsigned INT_TYPE abs_ival, t; \ + int ndigits; \ + /* Handle small and medium cases. */ \ + if (IS_SMALL_INT(ival)) { \ + return get_small_int((sdigit)ival); \ + } \ + if (-(INT_TYPE)PyLong_MASK <= ival && ival <= (INT_TYPE)PyLong_MASK) { \ + return _PyLong_FromMedium((sdigit)ival); \ + } \ + /* Count digits (at least two - smaller cases were handled above). */ \ + abs_ival = ival < 0 ? 0U-(unsigned INT_TYPE)ival : (unsigned INT_TYPE)ival; \ + /* Do shift in two steps to avoid possible undefined behavior. */ \ + t = abs_ival >> PyLong_SHIFT >> PyLong_SHIFT; \ + ndigits = 2; \ + while (t) { \ + ++ndigits; \ + t >>= PyLong_SHIFT; \ + } \ + /* Construct output value. */ \ + v = long_alloc(ndigits); \ + if (v != NULL) { \ + digit *p = v->long_value.ob_digit; \ + _PyLong_SetSignAndDigitCount(v, ival < 0 ? -1 : 1, ndigits); \ + t = abs_ival; \ + while (t) { \ + *p++ = (digit)(t & PyLong_MASK); \ + t >>= PyLong_SHIFT; \ + } \ + } \ + return (PyObject *)v; \ + } while(0) + + /* Create a new int object from a C long int */ PyObject * PyLong_FromLong(long ival) { - PyLongObject *v; - unsigned long abs_ival, t; - int ndigits; - - /* Handle small and medium cases. */ - if (IS_SMALL_INT(ival)) { - return get_small_int((sdigit)ival); - } - if (-(long)PyLong_MASK <= ival && ival <= (long)PyLong_MASK) { - return _PyLong_FromMedium((sdigit)ival); - } - - /* Count digits (at least two - smaller cases were handled above). */ - abs_ival = ival < 0 ? 0U-(unsigned long)ival : (unsigned long)ival; - /* Do shift in two steps to avoid possible undefined behavior. */ - t = abs_ival >> PyLong_SHIFT >> PyLong_SHIFT; - ndigits = 2; - while (t) { - ++ndigits; - t >>= PyLong_SHIFT; - } - - /* Construct output value. */ - v = long_alloc(ndigits); - if (v != NULL) { - digit *p = v->long_value.ob_digit; - _PyLong_SetSignAndDigitCount(v, ival < 0 ? -1 : 1, ndigits); - t = abs_ival; - while (t) { - *p++ = (digit)(t & PyLong_MASK); - t >>= PyLong_SHIFT; - } - } - return (PyObject *)v; + PYLONG_FROM_INT(long, ival); } #define PYLONG_FROM_UINT(INT_TYPE, ival) \ @@ -1482,40 +1485,7 @@ PyLong_AsVoidPtr(PyObject *vv) PyObject * PyLong_FromLongLong(long long ival) { - PyLongObject *v; - unsigned long long abs_ival, t; - int ndigits; - - /* Handle small and medium cases. */ - if (IS_SMALL_INT(ival)) { - return get_small_int((sdigit)ival); - } - if (-(long long)PyLong_MASK <= ival && ival <= (long long)PyLong_MASK) { - return _PyLong_FromMedium((sdigit)ival); - } - - /* Count digits (at least two - smaller cases were handled above). */ - abs_ival = ival < 0 ? 0U-(unsigned long long)ival : (unsigned long long)ival; - /* Do shift in two steps to avoid possible undefined behavior. */ - t = abs_ival >> PyLong_SHIFT >> PyLong_SHIFT; - ndigits = 2; - while (t) { - ++ndigits; - t >>= PyLong_SHIFT; - } - - /* Construct output value. */ - v = long_alloc(ndigits); - if (v != NULL) { - digit *p = v->long_value.ob_digit; - _PyLong_SetSignAndDigitCount(v, ival < 0 ? -1 : 1, ndigits); - t = abs_ival; - while (t) { - *p++ = (digit)(t & PyLong_MASK); - t >>= PyLong_SHIFT; - } - } - return (PyObject *)v; + PYLONG_FROM_INT(long long, ival); } /* Create a new int object from a C Py_ssize_t. */ From b4ee2afeb9d89108cd01a000384ee374665100b1 Mon Sep 17 00:00:00 2001 From: chris-eibl <138194463+chris-eibl@users.noreply.github.com> Date: Sat, 25 Jan 2025 19:58:09 +0100 Subject: [PATCH 2/9] Add UINT_TYPE to the macro PYLONG_FROM_INT. Use it in now in PyLong_FromSsize_t(), too. --- Objects/longobject.c | 47 ++++++-------------------------------------- 1 file changed, 6 insertions(+), 41 deletions(-) diff --git a/Objects/longobject.c b/Objects/longobject.c index db08a4bae5f6c6..8c95491daba500 100644 --- a/Objects/longobject.c +++ b/Objects/longobject.c @@ -332,10 +332,10 @@ _PyLong_Negate(PyLongObject **x_p) Py_DECREF(x); } -#define PYLONG_FROM_INT(INT_TYPE, ival) \ +#define PYLONG_FROM_INT(UINT_TYPE, INT_TYPE, ival) \ do { \ PyLongObject *v; \ - unsigned INT_TYPE abs_ival, t; \ + UINT_TYPE abs_ival, t; \ int ndigits; \ /* Handle small and medium cases. */ \ if (IS_SMALL_INT(ival)) { \ @@ -345,7 +345,7 @@ _PyLong_Negate(PyLongObject **x_p) return _PyLong_FromMedium((sdigit)ival); \ } \ /* Count digits (at least two - smaller cases were handled above). */ \ - abs_ival = ival < 0 ? 0U-(unsigned INT_TYPE)ival : (unsigned INT_TYPE)ival; \ + abs_ival = ival < 0 ? 0U-(UINT_TYPE)ival : (UINT_TYPE)ival; \ /* Do shift in two steps to avoid possible undefined behavior. */ \ t = abs_ival >> PyLong_SHIFT >> PyLong_SHIFT; \ ndigits = 2; \ @@ -373,7 +373,7 @@ _PyLong_Negate(PyLongObject **x_p) PyObject * PyLong_FromLong(long ival) { - PYLONG_FROM_INT(long, ival); + PYLONG_FROM_INT(unsigned long, long, ival); } #define PYLONG_FROM_UINT(INT_TYPE, ival) \ @@ -1485,7 +1485,7 @@ PyLong_AsVoidPtr(PyObject *vv) PyObject * PyLong_FromLongLong(long long ival) { - PYLONG_FROM_INT(long long, ival); + PYLONG_FROM_INT(unsigned long long, long long, ival); } /* Create a new int object from a C Py_ssize_t. */ @@ -1493,42 +1493,7 @@ PyLong_FromLongLong(long long ival) PyObject * PyLong_FromSsize_t(Py_ssize_t ival) { - PyLongObject *v; - size_t abs_ival; - size_t t; /* unsigned so >> doesn't propagate sign bit */ - int ndigits = 0; - int negative = 0; - - if (IS_SMALL_INT(ival)) { - return get_small_int((sdigit)ival); - } - - if (ival < 0) { - /* avoid signed overflow when ival = SIZE_T_MIN */ - abs_ival = (size_t)(-1-ival)+1; - negative = 1; - } - else { - abs_ival = (size_t)ival; - } - - /* Count the number of Python digits. */ - t = abs_ival; - while (t) { - ++ndigits; - t >>= PyLong_SHIFT; - } - v = long_alloc(ndigits); - if (v != NULL) { - digit *p = v->long_value.ob_digit; - _PyLong_SetSignAndDigitCount(v, negative ? -1 : 1, ndigits); - t = abs_ival; - while (t) { - *p++ = (digit)(t & PyLong_MASK); - t >>= PyLong_SHIFT; - } - } - return (PyObject *)v; + PYLONG_FROM_INT(size_t, Py_ssize_t, ival); } /* Get a C long long int from an int object or any object that has an From cf9244c4931aa6d10bf781f574283450e7dd39be Mon Sep 17 00:00:00 2001 From: chris-eibl <138194463+chris-eibl@users.noreply.github.com> Date: Sat, 25 Jan 2025 20:11:48 +0100 Subject: [PATCH 3/9] blurb it --- .../2025-01-25-20-07-03.gh-issue-129149.njeFJi.rst | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 Misc/NEWS.d/next/Core_and_Builtins/2025-01-25-20-07-03.gh-issue-129149.njeFJi.rst diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2025-01-25-20-07-03.gh-issue-129149.njeFJi.rst b/Misc/NEWS.d/next/Core_and_Builtins/2025-01-25-20-07-03.gh-issue-129149.njeFJi.rst new file mode 100644 index 00000000000000..b74607986d9a2e --- /dev/null +++ b/Misc/NEWS.d/next/Core_and_Builtins/2025-01-25-20-07-03.gh-issue-129149.njeFJi.rst @@ -0,0 +1,2 @@ +Add fast path for medium-size integers in :c:func:`PyLong_FromSsize_t`. +Patch by Chris Eibl. From ed04f4a538386cdf9562935874a4a5781fb40e59 Mon Sep 17 00:00:00 2001 From: Chris Eibl <138194463+chris-eibl@users.noreply.github.com> Date: Sun, 26 Jan 2025 06:22:41 +0100 Subject: [PATCH 4/9] Address Sergey's review Co-authored-by: Sergey B Kirpichev --- Objects/longobject.c | 3 --- 1 file changed, 3 deletions(-) diff --git a/Objects/longobject.c b/Objects/longobject.c index 8c95491daba500..1cabf734c003b0 100644 --- a/Objects/longobject.c +++ b/Objects/longobject.c @@ -334,9 +334,6 @@ _PyLong_Negate(PyLongObject **x_p) #define PYLONG_FROM_INT(UINT_TYPE, INT_TYPE, ival) \ do { \ - PyLongObject *v; \ - UINT_TYPE abs_ival, t; \ - int ndigits; \ /* Handle small and medium cases. */ \ if (IS_SMALL_INT(ival)) { \ return get_small_int((sdigit)ival); \ From c59bef098566fcb9140746aebd9f572ea270a7f4 Mon Sep 17 00:00:00 2001 From: Chris Eibl <138194463+chris-eibl@users.noreply.github.com> Date: Sun, 26 Jan 2025 06:22:51 +0100 Subject: [PATCH 5/9] Address Sergey's review Co-authored-by: Sergey B Kirpichev --- Objects/longobject.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Objects/longobject.c b/Objects/longobject.c index 1cabf734c003b0..195095acf8e190 100644 --- a/Objects/longobject.c +++ b/Objects/longobject.c @@ -351,7 +351,7 @@ _PyLong_Negate(PyLongObject **x_p) t >>= PyLong_SHIFT; \ } \ /* Construct output value. */ \ - v = long_alloc(ndigits); \ + PyLongObject *v = long_alloc(ndigits); \ if (v != NULL) { \ digit *p = v->long_value.ob_digit; \ _PyLong_SetSignAndDigitCount(v, ival < 0 ? -1 : 1, ndigits); \ From 98b3668e43809ce5f1338dd118888e0e7841b6f6 Mon Sep 17 00:00:00 2001 From: Chris Eibl <138194463+chris-eibl@users.noreply.github.com> Date: Sun, 26 Jan 2025 06:23:03 +0100 Subject: [PATCH 6/9] Address Sergey's review Co-authored-by: Sergey B Kirpichev --- Objects/longobject.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Objects/longobject.c b/Objects/longobject.c index 195095acf8e190..f805c5495dba5b 100644 --- a/Objects/longobject.c +++ b/Objects/longobject.c @@ -342,10 +342,10 @@ _PyLong_Negate(PyLongObject **x_p) return _PyLong_FromMedium((sdigit)ival); \ } \ /* Count digits (at least two - smaller cases were handled above). */ \ - abs_ival = ival < 0 ? 0U-(UINT_TYPE)ival : (UINT_TYPE)ival; \ + UINT_TYPE abs_ival = ival < 0 ? 0U-(UINT_TYPE)ival : (UINT_TYPE)ival; \ /* Do shift in two steps to avoid possible undefined behavior. */ \ - t = abs_ival >> PyLong_SHIFT >> PyLong_SHIFT; \ - ndigits = 2; \ + UINT_TYPE t = abs_ival >> PyLong_SHIFT >> PyLong_SHIFT; \ + int ndigits = 2; \ while (t) { \ ++ndigits; \ t >>= PyLong_SHIFT; \ From ff57cf7f61ab6ad607041704d87c67a37caaac63 Mon Sep 17 00:00:00 2001 From: chris-eibl <138194463+chris-eibl@users.noreply.github.com> Date: Sun, 26 Jan 2025 06:41:43 +0100 Subject: [PATCH 7/9] More stylistic syncs with PYLONG_FROM_UINT --- Objects/longobject.c | 33 ++++++++++++++++++--------------- 1 file changed, 18 insertions(+), 15 deletions(-) diff --git a/Objects/longobject.c b/Objects/longobject.c index f805c5495dba5b..7705b2581e29a6 100644 --- a/Objects/longobject.c +++ b/Objects/longobject.c @@ -336,30 +336,31 @@ _PyLong_Negate(PyLongObject **x_p) do { \ /* Handle small and medium cases. */ \ if (IS_SMALL_INT(ival)) { \ - return get_small_int((sdigit)ival); \ + return get_small_int((sdigit)(ival)); \ } \ - if (-(INT_TYPE)PyLong_MASK <= ival && ival <= (INT_TYPE)PyLong_MASK) { \ - return _PyLong_FromMedium((sdigit)ival); \ + if (-(INT_TYPE)PyLong_MASK <= (ival) && (ival) <= (INT_TYPE)PyLong_MASK) { \ + return _PyLong_FromMedium((sdigit)(ival)); \ } \ - /* Count digits (at least two - smaller cases were handled above). */ \ - UINT_TYPE abs_ival = ival < 0 ? 0U-(UINT_TYPE)ival : (UINT_TYPE)ival; \ + UINT_TYPE abs_ival = (ival) < 0 ? 0U-(UINT_TYPE)(ival) : (UINT_TYPE)(ival); \ /* Do shift in two steps to avoid possible undefined behavior. */ \ UINT_TYPE t = abs_ival >> PyLong_SHIFT >> PyLong_SHIFT; \ - int ndigits = 2; \ + /* Count digits (at least two - smaller cases were handled above). */ \ + Py_ssize_t ndigits = 2; \ while (t) { \ ++ndigits; \ t >>= PyLong_SHIFT; \ } \ /* Construct output value. */ \ PyLongObject *v = long_alloc(ndigits); \ - if (v != NULL) { \ - digit *p = v->long_value.ob_digit; \ - _PyLong_SetSignAndDigitCount(v, ival < 0 ? -1 : 1, ndigits); \ - t = abs_ival; \ - while (t) { \ - *p++ = (digit)(t & PyLong_MASK); \ - t >>= PyLong_SHIFT; \ - } \ + if (v == NULL) { \ + return NULL; \ + } \ + digit *p = v->long_value.ob_digit; \ + _PyLong_SetSignAndDigitCount(v, (ival) < 0 ? -1 : 1, ndigits); \ + t = abs_ival; \ + while (t) { \ + *p++ = (digit)(t & PyLong_MASK); \ + t >>= PyLong_SHIFT; \ } \ return (PyObject *)v; \ } while(0) @@ -375,6 +376,7 @@ PyLong_FromLong(long ival) #define PYLONG_FROM_UINT(INT_TYPE, ival) \ do { \ + /* Handle small and medium cases. */ \ if (IS_SMALL_UINT(ival)) { \ return get_small_int((sdigit)(ival)); \ } \ @@ -383,12 +385,13 @@ PyLong_FromLong(long ival) } \ /* Do shift in two steps to avoid possible undefined behavior. */ \ INT_TYPE t = (ival) >> PyLong_SHIFT >> PyLong_SHIFT; \ - /* Count the number of Python digits. */ \ + /* Count digits (at least two - smaller cases were handled above). */ \ Py_ssize_t ndigits = 2; \ while (t) { \ ++ndigits; \ t >>= PyLong_SHIFT; \ } \ + /* Construct output value. */ \ PyLongObject *v = long_alloc(ndigits); \ if (v == NULL) { \ return NULL; \ From f0691c5c9358fe9926d86d814507fc0ed221d9e3 Mon Sep 17 00:00:00 2001 From: Chris Eibl <138194463+chris-eibl@users.noreply.github.com> Date: Sat, 1 Mar 2025 12:18:04 +0100 Subject: [PATCH 8/9] address PEP-7 by vertically aligning \ (on a multiple of 4 spaces) --- Objects/longobject.c | 60 ++++++++++++++++++++++---------------------- 1 file changed, 30 insertions(+), 30 deletions(-) diff --git a/Objects/longobject.c b/Objects/longobject.c index 966c4d85e5cf45..49ee911cada225 100644 --- a/Objects/longobject.c +++ b/Objects/longobject.c @@ -332,37 +332,37 @@ _PyLong_Negate(PyLongObject **x_p) Py_DECREF(x); } -#define PYLONG_FROM_INT(UINT_TYPE, INT_TYPE, ival) \ - do { \ - /* Handle small and medium cases. */ \ - if (IS_SMALL_INT(ival)) { \ - return get_small_int((sdigit)(ival)); \ - } \ - if (-(INT_TYPE)PyLong_MASK <= (ival) && (ival) <= (INT_TYPE)PyLong_MASK) { \ - return _PyLong_FromMedium((sdigit)(ival)); \ - } \ +#define PYLONG_FROM_INT(UINT_TYPE, INT_TYPE, ival) \ + do { \ + /* Handle small and medium cases. */ \ + if (IS_SMALL_INT(ival)) { \ + return get_small_int((sdigit)(ival)); \ + } \ + if (-(INT_TYPE)PyLong_MASK <= (ival) && (ival) <= (INT_TYPE)PyLong_MASK) { \ + return _PyLong_FromMedium((sdigit)(ival)); \ + } \ UINT_TYPE abs_ival = (ival) < 0 ? 0U-(UINT_TYPE)(ival) : (UINT_TYPE)(ival); \ - /* Do shift in two steps to avoid possible undefined behavior. */ \ - UINT_TYPE t = abs_ival >> PyLong_SHIFT >> PyLong_SHIFT; \ - /* Count digits (at least two - smaller cases were handled above). */ \ - Py_ssize_t ndigits = 2; \ - while (t) { \ - ++ndigits; \ - t >>= PyLong_SHIFT; \ - } \ - /* Construct output value. */ \ - PyLongObject *v = long_alloc(ndigits); \ - if (v == NULL) { \ - return NULL; \ - } \ - digit *p = v->long_value.ob_digit; \ - _PyLong_SetSignAndDigitCount(v, (ival) < 0 ? -1 : 1, ndigits); \ - t = abs_ival; \ - while (t) { \ - *p++ = (digit)(t & PyLong_MASK); \ - t >>= PyLong_SHIFT; \ - } \ - return (PyObject *)v; \ + /* Do shift in two steps to avoid possible undefined behavior. */ \ + UINT_TYPE t = abs_ival >> PyLong_SHIFT >> PyLong_SHIFT; \ + /* Count digits (at least two - smaller cases were handled above). */ \ + Py_ssize_t ndigits = 2; \ + while (t) { \ + ++ndigits; \ + t >>= PyLong_SHIFT; \ + } \ + /* Construct output value. */ \ + PyLongObject *v = long_alloc(ndigits); \ + if (v == NULL) { \ + return NULL; \ + } \ + digit *p = v->long_value.ob_digit; \ + _PyLong_SetSignAndDigitCount(v, (ival) < 0 ? -1 : 1, ndigits); \ + t = abs_ival; \ + while (t) { \ + *p++ = (digit)(t & PyLong_MASK); \ + t >>= PyLong_SHIFT; \ + } \ + return (PyObject *)v; \ } while(0) From 261dcc53e1b01a93416075f0cc8316fd2c26f5f5 Mon Sep 17 00:00:00 2001 From: Chris Eibl <138194463+chris-eibl@users.noreply.github.com> Date: Sat, 1 Mar 2025 13:39:41 +0100 Subject: [PATCH 9/9] test more medium-size integers in all test_long_as* --- Lib/test/test_capi/test_long.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Lib/test/test_capi/test_long.py b/Lib/test/test_capi/test_long.py index d45ac75c822ea9..7bfb3a76c35893 100644 --- a/Lib/test/test_capi/test_long.py +++ b/Lib/test/test_capi/test_long.py @@ -168,9 +168,9 @@ def check_long_asint(self, func, min_val, max_val, *, mask=False, negative_value_error=OverflowError): # round trip (object -> C integer -> object) - values = (0, 1, 1234, max_val) + values = (0, 1, 512, 1234, max_val) if min_val < 0: - values += (-1, min_val) + values += (-1, -512, -1234, min_val) for value in values: with self.subTest(value=value): self.assertEqual(func(value), value)