From eb795fe562ce33fb45cb967318380576bec9df29 Mon Sep 17 00:00:00 2001 From: Koudai Aono Date: Tue, 25 Aug 2026 02:52:46 +0900 Subject: [PATCH] gh-100239: Specialize exact float division with BINARY_OP_EXTEND --- Lib/test/test_capi/test_opt.py | 12 +++-- Lib/test/test_opcache.py | 38 +++++++++++++ ...-08-25-00-15-00.gh-issue-100239.a7Hk2Q.rst | 2 + Python/bytecodes.c | 4 +- Python/optimizer_bytecodes.c | 53 +++++++++++++++++-- Python/optimizer_cases.c.h | 53 +++++++++++++++++-- Python/specialize.c | 17 ++++++ 7 files changed, 164 insertions(+), 15 deletions(-) create mode 100644 Misc/NEWS.d/next/Core_and_Builtins/2026-08-25-00-15-00.gh-issue-100239.a7Hk2Q.rst diff --git a/Lib/test/test_capi/test_opt.py b/Lib/test/test_capi/test_opt.py index 36efab518781410..88e5345590015d6 100644 --- a/Lib/test/test_capi/test_opt.py +++ b/Lib/test/test_capi/test_opt.py @@ -4037,11 +4037,11 @@ def testfunc(args): uops = get_opnames(ex) self.assertIn("_BINARY_OP_TRUEDIV_FLOAT_INPLACE_RIGHT", uops) - def test_float_truediv_speculative_guards_from_tracing(self): - # a, b are locals with no statically known type. _RECORD_TOS_TYPE / - # _RECORD_NOS_TYPE (added to the BINARY_OP macro) capture the observed - # operand types during tracing, and the optimizer then speculatively - # emits _GUARD_{TOS,NOS}_FLOAT and specializes the division. + def test_float_truediv_from_tier1_specialization(self): + # a, b are locals with no statically known type. The tier 1 + # BINARY_OP_EXTEND specialization supplies the exact operand types. + # The optimizer lowers its descriptor guard to direct float guards + # before specializing the division. def testfunc(args): a, b, n = args total = 0.0 @@ -4056,6 +4056,8 @@ def testfunc(args): self.assertIn("_GUARD_TOS_FLOAT", uops) self.assertIn("_GUARD_NOS_FLOAT", uops) self.assertIn("_BINARY_OP_TRUEDIV_FLOAT", uops) + self.assertNotIn("_GUARD_BINARY_OP_EXTEND", uops) + self.assertNotIn("_BINARY_OP_EXTEND", uops) def test_float_remainder_speculative_guards_from_tracing(self): # a, b are locals with no statically known type. Tracing records diff --git a/Lib/test/test_opcache.py b/Lib/test/test_opcache.py index 7946550ec0db637..f8fdeec2c51200f 100644 --- a/Lib/test/test_opcache.py +++ b/Lib/test/test_opcache.py @@ -1448,6 +1448,44 @@ def binary_op_add_extend(): self.assert_specialized(binary_op_add_extend, "BINARY_OP_EXTEND") self.assert_no_opcode(binary_op_add_extend, "BINARY_OP") + def float_true_divide(a, b): + return a / b + + def float_inplace_true_divide(a, b): + a /= b + return a + + for _ in range(_testinternalcapi.SPECIALIZATION_THRESHOLD): + self.assertEqual(float_true_divide(6.0, 3.0), 2.0) + self.assertEqual(float_inplace_true_divide(6.0, 3.0), 2.0) + + self.assert_specialized(float_true_divide, "BINARY_OP_EXTEND") + self.assert_specialized(float_inplace_true_divide, "BINARY_OP_EXTEND") + with self.assertRaises(ZeroDivisionError) as cm: + float_true_divide(1.0, 0.0) + self.assertEqual(str(cm.exception), "division by zero") + with self.assertRaises(ZeroDivisionError) as cm: + float_inplace_true_divide(1.0, -0.0) + self.assertEqual(str(cm.exception), "division by zero") + nan = float_true_divide(float("nan"), 1.0) + self.assertNotEqual(nan, nan) + + class FloatSubclass(float): + def __truediv__(self, other): + return "subclass truediv" + + def __rtruediv__(self, other): + return "subclass reflected truediv" + + self.assertEqual( + float_true_divide(FloatSubclass(6.0), 3.0), + "subclass truediv", + ) + self.assertEqual( + float_true_divide(6.0, FloatSubclass(3.0)), + "subclass reflected truediv", + ) + def binary_op_add_extend_sequences(): l1 = [1, 2] l2 = [None] diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2026-08-25-00-15-00.gh-issue-100239.a7Hk2Q.rst b/Misc/NEWS.d/next/Core_and_Builtins/2026-08-25-00-15-00.gh-issue-100239.a7Hk2Q.rst new file mode 100644 index 000000000000000..50ec31ef224098c --- /dev/null +++ b/Misc/NEWS.d/next/Core_and_Builtins/2026-08-25-00-15-00.gh-issue-100239.a7Hk2Q.rst @@ -0,0 +1,2 @@ +Specialize exact ``float`` true division in the tier 1 interpreter using +``BINARY_OP_EXTEND``. diff --git a/Python/bytecodes.c b/Python/bytecodes.c index fb0cdf4d65e060d..4d4cb2d03b78b4d 100644 --- a/Python/bytecodes.c +++ b/Python/bytecodes.c @@ -901,8 +901,8 @@ dummy_func( INPUTS_DEAD(); } - // Float true division --- not specialized at tier 1, emitted by the - // tier 2 optimizer when both operands are known floats. + // Float true division --- emitted by the tier 2 optimizer when both + // operands are known floats. tier2 op(_BINARY_OP_TRUEDIV_FLOAT, (left, right -- res, l, r)) { PyObject *left_o = PyStackRef_AsPyObjectBorrow(left); PyObject *right_o = PyStackRef_AsPyObjectBorrow(right); diff --git a/Python/optimizer_bytecodes.c b/Python/optimizer_bytecodes.c index 5246e50633461bd..eceba7e1894f11f 100644 --- a/Python/optimizer_bytecodes.c +++ b/Python/optimizer_bytecodes.c @@ -517,7 +517,27 @@ dummy_func(void) { assert(d->lhs_type != NULL && d->rhs_type != NULL); bool lhs_known = sym_matches_type(left, d->lhs_type); bool rhs_known = sym_matches_type(right, d->rhs_type); - if (lhs_known && rhs_known) { + bool is_float_truediv = ( + (d->oparg == NB_TRUE_DIVIDE || d->oparg == NB_INPLACE_TRUE_DIVIDE) && + d->lhs_type == &PyFloat_Type && + d->rhs_type == &PyFloat_Type && + d->result_type == &PyFloat_Type && + d->result_unique + ); + if (is_float_truediv) { + if (!rhs_known) { + ADD_OP(_GUARD_TOS_FLOAT, 0, 0); + sym_set_type(right, &PyFloat_Type); + } + if (!lhs_known) { + ADD_OP(_GUARD_NOS_FLOAT, 0, 0); + sym_set_type(left, &PyFloat_Type); + } + if (lhs_known && rhs_known) { + ADD_OP(_NOP, 0, 0); + } + } + else if (lhs_known && rhs_known) { ADD_OP(_NOP, 0, 0); } else if (lhs_known) { @@ -533,7 +553,34 @@ dummy_func(void) { op(_BINARY_OP_EXTEND, (descr/4, left, right -- res, l, r)) { _PyBinaryOpSpecializationDescr *d = (_PyBinaryOpSpecializationDescr *)descr; - if (d != NULL && d->result_type != NULL) { + l = left; + r = right; + bool is_float_truediv = ( + d != NULL && + d->guard == NULL && + (d->oparg == NB_TRUE_DIVIDE || d->oparg == NB_INPLACE_TRUE_DIVIDE) && + d->lhs_type == &PyFloat_Type && + d->rhs_type == &PyFloat_Type && + d->result_type == &PyFloat_Type && + d->result_unique + ); + if (is_float_truediv) { + if (PyJitRef_IsUnique(left)) { + ADD_OP(_BINARY_OP_TRUEDIV_FLOAT_INPLACE, 0, 0); + l = sym_new_null(ctx); + r = right; + } + else if (PyJitRef_IsUnique(right)) { + ADD_OP(_BINARY_OP_TRUEDIV_FLOAT_INPLACE_RIGHT, 0, 0); + l = left; + r = sym_new_null(ctx); + } + else { + ADD_OP(_BINARY_OP_TRUEDIV_FLOAT, 0, 0); + } + res = PyJitRef_MakeUnique(sym_new_type(ctx, &PyFloat_Type)); + } + else if (d != NULL && d->result_type != NULL) { res = sym_new_type(ctx, d->result_type); if (d->result_unique) { res = PyJitRef_MakeUnique(res); @@ -542,8 +589,6 @@ dummy_func(void) { else { res = sym_new_not_null(ctx); } - l = left; - r = right; } op(_BINARY_OP_INPLACE_ADD_UNICODE, (left, right -- res)) { diff --git a/Python/optimizer_cases.c.h b/Python/optimizer_cases.c.h index 21f275f27cafe04..5b32a1cabaa5760 100644 --- a/Python/optimizer_cases.c.h +++ b/Python/optimizer_cases.c.h @@ -1254,7 +1254,27 @@ assert(d->lhs_type != NULL && d->rhs_type != NULL); bool lhs_known = sym_matches_type(left, d->lhs_type); bool rhs_known = sym_matches_type(right, d->rhs_type); - if (lhs_known && rhs_known) { + bool is_float_truediv = ( + (d->oparg == NB_TRUE_DIVIDE || d->oparg == NB_INPLACE_TRUE_DIVIDE) && + d->lhs_type == &PyFloat_Type && + d->rhs_type == &PyFloat_Type && + d->result_type == &PyFloat_Type && + d->result_unique + ); + if (is_float_truediv) { + if (!rhs_known) { + ADD_OP(_GUARD_TOS_FLOAT, 0, 0); + sym_set_type(right, &PyFloat_Type); + } + if (!lhs_known) { + ADD_OP(_GUARD_NOS_FLOAT, 0, 0); + sym_set_type(left, &PyFloat_Type); + } + if (lhs_known && rhs_known) { + ADD_OP(_NOP, 0, 0); + } + } + else if (lhs_known && rhs_known) { ADD_OP(_NOP, 0, 0); } else if (lhs_known) { @@ -1279,7 +1299,34 @@ left = stack_pointer[-2]; PyObject *descr = (PyObject *)this_instr->operand0; _PyBinaryOpSpecializationDescr *d = (_PyBinaryOpSpecializationDescr *)descr; - if (d != NULL && d->result_type != NULL) { + l = left; + r = right; + bool is_float_truediv = ( + d != NULL && + d->guard == NULL && + (d->oparg == NB_TRUE_DIVIDE || d->oparg == NB_INPLACE_TRUE_DIVIDE) && + d->lhs_type == &PyFloat_Type && + d->rhs_type == &PyFloat_Type && + d->result_type == &PyFloat_Type && + d->result_unique + ); + if (is_float_truediv) { + if (PyJitRef_IsUnique(left)) { + ADD_OP(_BINARY_OP_TRUEDIV_FLOAT_INPLACE, 0, 0); + l = sym_new_null(ctx); + r = right; + } + else if (PyJitRef_IsUnique(right)) { + ADD_OP(_BINARY_OP_TRUEDIV_FLOAT_INPLACE_RIGHT, 0, 0); + l = left; + r = sym_new_null(ctx); + } + else { + ADD_OP(_BINARY_OP_TRUEDIV_FLOAT, 0, 0); + } + res = PyJitRef_MakeUnique(sym_new_type(ctx, &PyFloat_Type)); + } + else if (d != NULL && d->result_type != NULL) { res = sym_new_type(ctx, d->result_type); if (d->result_unique) { res = PyJitRef_MakeUnique(res); @@ -1288,8 +1335,6 @@ else { res = sym_new_not_null(ctx); } - l = left; - r = right; CHECK_STACK_BOUNDS(1); stack_pointer[-2] = res; stack_pointer[-1] = l; diff --git a/Python/specialize.c b/Python/specialize.c index 05cb76ff015ff40..6d2787fb30fed12 100644 --- a/Python/specialize.c +++ b/Python/specialize.c @@ -2179,6 +2179,19 @@ BITWISE_LONGS_ACTION(compactlongs_and, &) BITWISE_LONGS_ACTION(compactlongs_xor, ^) #undef BITWISE_LONGS_ACTION +/* float-float */ + +static PyObject * +floats_true_div(PyObject *lhs, PyObject *rhs) +{ + double divisor = PyFloat_AS_DOUBLE(rhs); + if (divisor == 0.0) { + PyErr_SetString(PyExc_ZeroDivisionError, "division by zero"); + return NULL; + } + return PyFloat_FromDouble(PyFloat_AS_DOUBLE(lhs) / divisor); +} + /* float-long */ static inline int @@ -2259,6 +2272,10 @@ static _PyBinaryOpSpecializationDescr binaryop_extend_descrs[] = { {NB_INPLACE_AND, compactlongs_guard, compactlongs_and, &PyLong_Type, 1, NULL, NULL}, {NB_INPLACE_XOR, compactlongs_guard, compactlongs_xor, &PyLong_Type, 1, NULL, NULL}, + /* float-float true division */ + {NB_TRUE_DIVIDE, NULL, floats_true_div, &PyFloat_Type, 1, &PyFloat_Type, &PyFloat_Type}, + {NB_INPLACE_TRUE_DIVIDE, NULL, floats_true_div, &PyFloat_Type, 1, &PyFloat_Type, &PyFloat_Type}, + /* float-long arithmetic: guards also check NaN and compactness. */ {NB_ADD, float_compactlong_guard, float_compactlong_add, &PyFloat_Type, 1, NULL, NULL}, {NB_SUBTRACT, float_compactlong_guard, float_compactlong_subtract, &PyFloat_Type, 1, NULL, NULL},