From f6d6e50a9cf99a1760379fcdab1def026a58482f Mon Sep 17 00:00:00 2001 From: Pieter Eendebak Date: Wed, 29 Jan 2025 11:12:04 +0100 Subject: [PATCH 01/10] specialize rshift on ints --- Python/specialize.c | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/Python/specialize.c b/Python/specialize.c index abb130d73eeebd..c4a28365ff3fd2 100644 --- a/Python/specialize.c +++ b/Python/specialize.c @@ -585,6 +585,8 @@ _PyCode_Quicken(_Py_CODEUNIT *instructions, Py_ssize_t size, PyObject *consts, #define SPEC_FAIL_BINARY_OP_OR_DIFFERENT_TYPES 30 #define SPEC_FAIL_BINARY_OP_XOR_INT 31 #define SPEC_FAIL_BINARY_OP_XOR_DIFFERENT_TYPES 32 +#define SPEC_FAIL_BINARY_OP_RSHIFT_INT 33 +#define SPEC_FAIL_BINARY_OP_RSHIFT_DIFFERENT_TYPES 34 /* Calls */ @@ -2398,6 +2400,12 @@ binary_op_fail_kind(int oparg, PyObject *lhs, PyObject *rhs) return SPEC_FAIL_BINARY_OP_REMAINDER; case NB_RSHIFT: case NB_INPLACE_RSHIFT: + if (!Py_IS_TYPE(lhs, Py_TYPE(rhs))) { + return SPEC_FAIL_BINARY_OP_RSHIFT_DIFFERENT_TYPES; + } + if (PyLong_CheckExact(lhs)) { + return SPEC_FAIL_BINARY_OP_RSHIFT_INT; + } return SPEC_FAIL_BINARY_OP_RSHIFT; case NB_SUBTRACT: case NB_INPLACE_SUBTRACT: @@ -2456,6 +2464,7 @@ compactlongs_guard(PyObject *lhs, PyObject *rhs) BITWISE_LONGS_ACTION(compactlongs_or, |) BITWISE_LONGS_ACTION(compactlongs_and, &) BITWISE_LONGS_ACTION(compactlongs_xor, ^) +BITWISE_LONGS_ACTION(compactlongs_rshift, >>) #undef BITWISE_LONGS_ACTION /* float-long */ @@ -2532,9 +2541,11 @@ static _PyBinaryOpSpecializationDescr compactlongs_specs[NB_OPARG_LAST+1] = { [NB_OR] = {compactlongs_guard, compactlongs_or}, [NB_AND] = {compactlongs_guard, compactlongs_and}, [NB_XOR] = {compactlongs_guard, compactlongs_xor}, + [NB_RSHIFT] = {compactlongs_guard, compactlongs_rshift}, [NB_INPLACE_OR] = {compactlongs_guard, compactlongs_or}, [NB_INPLACE_AND] = {compactlongs_guard, compactlongs_and}, [NB_INPLACE_XOR] = {compactlongs_guard, compactlongs_xor}, + [NB_INPLACE_RSHIFT] = {compactlongs_guard, compactlongs_rshift}, }; static _PyBinaryOpSpecializationDescr float_compactlong_specs[NB_OPARG_LAST+1] = { From dd5347028cae18ce3d41d22a3f271cb484f8f4c6 Mon Sep 17 00:00:00 2001 From: Pieter Eendebak Date: Wed, 29 Jan 2025 11:15:28 +0100 Subject: [PATCH 02/10] add test --- Lib/test/test_opcache.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Lib/test/test_opcache.py b/Lib/test/test_opcache.py index dc02d1d7babb23..20ea4480f9129f 100644 --- a/Lib/test/test_opcache.py +++ b/Lib/test/test_opcache.py @@ -1414,6 +1414,12 @@ def binary_op_bitwise_extend(): a, b = 3, 9 a ^= b self.assertEqual(a, 10) + a, b = 10, 2 + a = a >> b + self.assertEqual(a, 2) + a, b = 10, 2 + a >>= b + self.assertEqual(a, 2) binary_op_bitwise_extend() self.assert_specialized(binary_op_bitwise_extend, "BINARY_OP_EXTEND") From ca60ccc34046a166866fabc88c8f91b729cd0ef4 Mon Sep 17 00:00:00 2001 From: Pieter Eendebak Date: Wed, 29 Jan 2025 12:15:34 +0100 Subject: [PATCH 03/10] handle case of negative shift --- Lib/test/test_opcache.py | 10 ++++++++++ Python/specialize.c | 17 +++++++++++++++-- 2 files changed, 25 insertions(+), 2 deletions(-) diff --git a/Lib/test/test_opcache.py b/Lib/test/test_opcache.py index 20ea4480f9129f..7ff78459c7f6da 100644 --- a/Lib/test/test_opcache.py +++ b/Lib/test/test_opcache.py @@ -1425,6 +1425,16 @@ def binary_op_bitwise_extend(): self.assert_specialized(binary_op_bitwise_extend, "BINARY_OP_EXTEND") self.assert_no_opcode(binary_op_bitwise_extend, "BINARY_OP") + for idx in range(100): + a, b = 2, 1 + if idx == 99: + b = -1 + try: + a >> b + except ValueError: + assert b == -1 + self.assertEqual(a, 1) + @cpython_only @requires_specialization_ft def test_load_super_attr(self): diff --git a/Python/specialize.c b/Python/specialize.c index c4a28365ff3fd2..3190a46604c82e 100644 --- a/Python/specialize.c +++ b/Python/specialize.c @@ -2447,12 +2447,25 @@ is_compactlong(PyObject *v) _PyLong_IsCompact((PyLongObject *)v); } +static inline int +is_compactnonnegativelong(PyObject *v) +{ + return PyLong_CheckExact(v) && + _PyLong_IsNonNegativeCompact((PyLongObject *)v); +} + static int compactlongs_guard(PyObject *lhs, PyObject *rhs) { return (is_compactlong(lhs) && is_compactlong(rhs)); } +static int +compactlong_compactnonnegativelong_guard(PyObject *lhs, PyObject *rhs) +{ + return (is_compactlong(lhs) && is_compactnonnegativelong(rhs); +} + #define BITWISE_LONGS_ACTION(NAME, OP) \ static PyObject * \ (NAME)(PyObject *lhs, PyObject *rhs) \ @@ -2541,11 +2554,11 @@ static _PyBinaryOpSpecializationDescr compactlongs_specs[NB_OPARG_LAST+1] = { [NB_OR] = {compactlongs_guard, compactlongs_or}, [NB_AND] = {compactlongs_guard, compactlongs_and}, [NB_XOR] = {compactlongs_guard, compactlongs_xor}, - [NB_RSHIFT] = {compactlongs_guard, compactlongs_rshift}, + [NB_RSHIFT] = {compactlong_compactnonnegativelong_guard, compactlongs_rshift}, [NB_INPLACE_OR] = {compactlongs_guard, compactlongs_or}, [NB_INPLACE_AND] = {compactlongs_guard, compactlongs_and}, [NB_INPLACE_XOR] = {compactlongs_guard, compactlongs_xor}, - [NB_INPLACE_RSHIFT] = {compactlongs_guard, compactlongs_rshift}, + [NB_INPLACE_RSHIFT] = {compactlong_compactnonnegativelong_guard, compactlongs_rshift}, }; static _PyBinaryOpSpecializationDescr float_compactlong_specs[NB_OPARG_LAST+1] = { From 98015ac67a6c09304a88663eee8461a2143ad8a7 Mon Sep 17 00:00:00 2001 From: Pieter Eendebak Date: Wed, 29 Jan 2025 12:16:16 +0100 Subject: [PATCH 04/10] handle case of negative shift --- Lib/test/test_opcache.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Lib/test/test_opcache.py b/Lib/test/test_opcache.py index 7ff78459c7f6da..81dafe6c0e124a 100644 --- a/Lib/test/test_opcache.py +++ b/Lib/test/test_opcache.py @@ -1430,10 +1430,10 @@ def binary_op_bitwise_extend(): if idx == 99: b = -1 try: - a >> b + z = a >> b except ValueError: assert b == -1 - self.assertEqual(a, 1) + self.assertEqual(z, 1) @cpython_only @requires_specialization_ft From 70ad42a3c43f60c82a38b159953b4b56bf0c7e63 Mon Sep 17 00:00:00 2001 From: Pieter Eendebak Date: Wed, 29 Jan 2025 12:19:51 +0100 Subject: [PATCH 05/10] handle case of negative shift --- Lib/test/test_opcache.py | 1 + Python/specialize.c | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/Lib/test/test_opcache.py b/Lib/test/test_opcache.py index 81dafe6c0e124a..c75c653c611009 100644 --- a/Lib/test/test_opcache.py +++ b/Lib/test/test_opcache.py @@ -1425,6 +1425,7 @@ def binary_op_bitwise_extend(): self.assert_specialized(binary_op_bitwise_extend, "BINARY_OP_EXTEND") self.assert_no_opcode(binary_op_bitwise_extend, "BINARY_OP") + # check that after specialization of >> we handle negative shifts for idx in range(100): a, b = 2, 1 if idx == 99: diff --git a/Python/specialize.c b/Python/specialize.c index 3190a46604c82e..a241d3f77b38ab 100644 --- a/Python/specialize.c +++ b/Python/specialize.c @@ -2463,7 +2463,7 @@ compactlongs_guard(PyObject *lhs, PyObject *rhs) static int compactlong_compactnonnegativelong_guard(PyObject *lhs, PyObject *rhs) { - return (is_compactlong(lhs) && is_compactnonnegativelong(rhs); + return (is_compactlong(lhs) && is_compactnonnegativelong(rhs)); } #define BITWISE_LONGS_ACTION(NAME, OP) \ From 004dce0408176668327ef2c8592249a33b0150d5 Mon Sep 17 00:00:00 2001 From: Pieter Eendebak Date: Wed, 29 Jan 2025 21:06:48 +0100 Subject: [PATCH 06/10] add lshift; avoid undefined behavior --- Lib/test/test_opcache.py | 6 ++++++ Python/specialize.c | 23 +++++++++++++++++++---- 2 files changed, 25 insertions(+), 4 deletions(-) diff --git a/Lib/test/test_opcache.py b/Lib/test/test_opcache.py index 163d3f38e474ce..cbfa0aafe9c240 100644 --- a/Lib/test/test_opcache.py +++ b/Lib/test/test_opcache.py @@ -1422,6 +1422,12 @@ def binary_op_bitwise_extend(): a, b = 10, 2 a >>= b self.assertEqual(a, 2) + a, b = 10, 2 + a = a << b + self.assertEqual(a, 40) + a, b = 10, 2 + a <<= b + self.assertEqual(a, 40) binary_op_bitwise_extend() self.assert_specialized(binary_op_bitwise_extend, "BINARY_OP_EXTEND") diff --git a/Python/specialize.c b/Python/specialize.c index a241d3f77b38ab..b7813b351378a0 100644 --- a/Python/specialize.c +++ b/Python/specialize.c @@ -585,6 +585,8 @@ _PyCode_Quicken(_Py_CODEUNIT *instructions, Py_ssize_t size, PyObject *consts, #define SPEC_FAIL_BINARY_OP_OR_DIFFERENT_TYPES 30 #define SPEC_FAIL_BINARY_OP_XOR_INT 31 #define SPEC_FAIL_BINARY_OP_XOR_DIFFERENT_TYPES 32 +#define SPEC_FAIL_BINARY_OP_LSHIFT_INT 33 +#define SPEC_FAIL_BINARY_OP_LSHIFT_DIFFERENT_TYPES 34 #define SPEC_FAIL_BINARY_OP_RSHIFT_INT 33 #define SPEC_FAIL_BINARY_OP_RSHIFT_DIFFERENT_TYPES 34 @@ -2373,6 +2375,12 @@ binary_op_fail_kind(int oparg, PyObject *lhs, PyObject *rhs) return SPEC_FAIL_BINARY_OP_FLOOR_DIVIDE; case NB_LSHIFT: case NB_INPLACE_LSHIFT: + if (!Py_IS_TYPE(lhs, Py_TYPE(rhs))) { + return SPEC_FAIL_BINARY_OP_LSHIFT_DIFFERENT_TYPES; + } + if (PyLong_CheckExact(lhs)) { + return SPEC_FAIL_BINARY_OP_LSHIFT_INT; + } return SPEC_FAIL_BINARY_OP_LSHIFT; case NB_MATRIX_MULTIPLY: case NB_INPLACE_MATRIX_MULTIPLY: @@ -2461,9 +2469,13 @@ compactlongs_guard(PyObject *lhs, PyObject *rhs) } static int -compactlong_compactnonnegativelong_guard(PyObject *lhs, PyObject *rhs) +shift_guard(PyObject *lhs, PyObject *rhs) { - return (is_compactlong(lhs) && is_compactnonnegativelong(rhs)); + // we could use _long_is_small_int here, which is slightly faster than is_compactnonnegativelong + + // rshift with value larger the the number of bits is undefined in C + // for lshift we do not want to overflow, but we always have at least 16 bits available + return (is_compactlong(lhs) && is_compactnonnegativelong(rhs) && (_PyLong_CompactValue((PyLongObject *)rhs) <= 16) ); } #define BITWISE_LONGS_ACTION(NAME, OP) \ @@ -2477,6 +2489,7 @@ compactlong_compactnonnegativelong_guard(PyObject *lhs, PyObject *rhs) BITWISE_LONGS_ACTION(compactlongs_or, |) BITWISE_LONGS_ACTION(compactlongs_and, &) BITWISE_LONGS_ACTION(compactlongs_xor, ^) +BITWISE_LONGS_ACTION(compactlongs_lshift, <<) BITWISE_LONGS_ACTION(compactlongs_rshift, >>) #undef BITWISE_LONGS_ACTION @@ -2554,11 +2567,13 @@ static _PyBinaryOpSpecializationDescr compactlongs_specs[NB_OPARG_LAST+1] = { [NB_OR] = {compactlongs_guard, compactlongs_or}, [NB_AND] = {compactlongs_guard, compactlongs_and}, [NB_XOR] = {compactlongs_guard, compactlongs_xor}, - [NB_RSHIFT] = {compactlong_compactnonnegativelong_guard, compactlongs_rshift}, + [NB_LSHIFT] = {shift_guard, compactlongs_lshift}, + [NB_RSHIFT] = {shift_guard, compactlongs_rshift}, [NB_INPLACE_OR] = {compactlongs_guard, compactlongs_or}, [NB_INPLACE_AND] = {compactlongs_guard, compactlongs_and}, [NB_INPLACE_XOR] = {compactlongs_guard, compactlongs_xor}, - [NB_INPLACE_RSHIFT] = {compactlong_compactnonnegativelong_guard, compactlongs_rshift}, + [NB_INPLACE_LSHIFT] = {shift_guard, compactlongs_lshift}, + [NB_INPLACE_RSHIFT] = {shift_guard, compactlongs_rshift}, }; static _PyBinaryOpSpecializationDescr float_compactlong_specs[NB_OPARG_LAST+1] = { From 32c68aefa4d5095030b1aef87de42d95da8fc979 Mon Sep 17 00:00:00 2001 From: Pieter Eendebak Date: Wed, 29 Jan 2025 22:40:36 +0100 Subject: [PATCH 07/10] tighten guard --- Python/specialize.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Python/specialize.c b/Python/specialize.c index b7813b351378a0..9d4c1796e92bb5 100644 --- a/Python/specialize.c +++ b/Python/specialize.c @@ -2475,7 +2475,7 @@ shift_guard(PyObject *lhs, PyObject *rhs) // rshift with value larger the the number of bits is undefined in C // for lshift we do not want to overflow, but we always have at least 16 bits available - return (is_compactlong(lhs) && is_compactnonnegativelong(rhs) && (_PyLong_CompactValue((PyLongObject *)rhs) <= 16) ); + return (is_compactlong(lhs) && is_compactnonnegativelong(rhs) && (_PyLong_CompactValue((PyLongObject *)rhs) <= 12) ); } #define BITWISE_LONGS_ACTION(NAME, OP) \ From 0085021ba4f98241d35473048d2f9e9978d36cdc Mon Sep 17 00:00:00 2001 From: Pieter Eendebak Date: Thu, 30 Jan 2025 20:53:26 +0100 Subject: [PATCH 08/10] use stwodigits for lshift --- Python/specialize.c | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/Python/specialize.c b/Python/specialize.c index 9d4c1796e92bb5..6026b15f2203b4 100644 --- a/Python/specialize.c +++ b/Python/specialize.c @@ -2475,9 +2475,18 @@ shift_guard(PyObject *lhs, PyObject *rhs) // rshift with value larger the the number of bits is undefined in C // for lshift we do not want to overflow, but we always have at least 16 bits available - return (is_compactlong(lhs) && is_compactnonnegativelong(rhs) && (_PyLong_CompactValue((PyLongObject *)rhs) <= 12) ); + return (is_compactlong(lhs) && is_compactnonnegativelong(rhs) && (_PyLong_CompactValue((PyLongObject *)rhs) <= 16) ); } +#define BITWISE_LONGS_ACTION_STWODIGITS(NAME, OP) \ + static PyObject * \ + (NAME)(PyObject *lhs, PyObject *rhs) \ + { \ + stwodigits rhs_val = (stwodigits)_PyLong_CompactValue((PyLongObject *)rhs); \ + stwodigits lhs_val = (stwodigits) _PyLong_CompactValue((PyLongObject *)lhs); \ + return PyLong_FromLongLong(lhs_val OP rhs_val); \ + } + #define BITWISE_LONGS_ACTION(NAME, OP) \ static PyObject * \ (NAME)(PyObject *lhs, PyObject *rhs) \ @@ -2489,7 +2498,7 @@ shift_guard(PyObject *lhs, PyObject *rhs) BITWISE_LONGS_ACTION(compactlongs_or, |) BITWISE_LONGS_ACTION(compactlongs_and, &) BITWISE_LONGS_ACTION(compactlongs_xor, ^) -BITWISE_LONGS_ACTION(compactlongs_lshift, <<) +BITWISE_LONGS_ACTION_STWODIGITS(compactlongs_lshift, <<) BITWISE_LONGS_ACTION(compactlongs_rshift, >>) #undef BITWISE_LONGS_ACTION From 3de02832738e14a71fcc764c74911b3d6498cfb4 Mon Sep 17 00:00:00 2001 From: "blurb-it[bot]" <43283697+blurb-it[bot]@users.noreply.github.com> Date: Thu, 30 Jan 2025 20:15:49 +0000 Subject: [PATCH 09/10] =?UTF-8?q?=F0=9F=93=9C=F0=9F=A4=96=20Added=20by=20b?= =?UTF-8?q?lurb=5Fit.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../2025-01-30-20-15-44.gh-issue-100239.7_XP-Y.rst | 1 + 1 file changed, 1 insertion(+) create mode 100644 Misc/NEWS.d/next/Core_and_Builtins/2025-01-30-20-15-44.gh-issue-100239.7_XP-Y.rst diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2025-01-30-20-15-44.gh-issue-100239.7_XP-Y.rst b/Misc/NEWS.d/next/Core_and_Builtins/2025-01-30-20-15-44.gh-issue-100239.7_XP-Y.rst new file mode 100644 index 00000000000000..d049549cc2ca98 --- /dev/null +++ b/Misc/NEWS.d/next/Core_and_Builtins/2025-01-30-20-15-44.gh-issue-100239.7_XP-Y.rst @@ -0,0 +1 @@ +Specialize ``BINARY_OP`` for left shift and right shift operations on compact ints. From 1895d6950c0b3dfc21725be521041f77216a54b6 Mon Sep 17 00:00:00 2001 From: Pieter Eendebak Date: Fri, 31 Jan 2025 11:09:15 +0100 Subject: [PATCH 10/10] add missing undef --- Python/specialize.c | 1 + 1 file changed, 1 insertion(+) diff --git a/Python/specialize.c b/Python/specialize.c index 6026b15f2203b4..fbef668f8c8311 100644 --- a/Python/specialize.c +++ b/Python/specialize.c @@ -2500,6 +2500,7 @@ BITWISE_LONGS_ACTION(compactlongs_and, &) BITWISE_LONGS_ACTION(compactlongs_xor, ^) BITWISE_LONGS_ACTION_STWODIGITS(compactlongs_lshift, <<) BITWISE_LONGS_ACTION(compactlongs_rshift, >>) +#undef BITWISE_LONGS_ACTION_STWODIGITS #undef BITWISE_LONGS_ACTION /* float-long */