Skip to content

Commit eb795fe

Browse files
committed
gh-100239: Specialize exact float division with BINARY_OP_EXTEND
1 parent ee1da7e commit eb795fe

7 files changed

Lines changed: 164 additions & 15 deletions

File tree

Lib/test/test_capi/test_opt.py

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4037,11 +4037,11 @@ def testfunc(args):
40374037
uops = get_opnames(ex)
40384038
self.assertIn("_BINARY_OP_TRUEDIV_FLOAT_INPLACE_RIGHT", uops)
40394039

4040-
def test_float_truediv_speculative_guards_from_tracing(self):
4041-
# a, b are locals with no statically known type. _RECORD_TOS_TYPE /
4042-
# _RECORD_NOS_TYPE (added to the BINARY_OP macro) capture the observed
4043-
# operand types during tracing, and the optimizer then speculatively
4044-
# emits _GUARD_{TOS,NOS}_FLOAT and specializes the division.
4040+
def test_float_truediv_from_tier1_specialization(self):
4041+
# a, b are locals with no statically known type. The tier 1
4042+
# BINARY_OP_EXTEND specialization supplies the exact operand types.
4043+
# The optimizer lowers its descriptor guard to direct float guards
4044+
# before specializing the division.
40454045
def testfunc(args):
40464046
a, b, n = args
40474047
total = 0.0
@@ -4056,6 +4056,8 @@ def testfunc(args):
40564056
self.assertIn("_GUARD_TOS_FLOAT", uops)
40574057
self.assertIn("_GUARD_NOS_FLOAT", uops)
40584058
self.assertIn("_BINARY_OP_TRUEDIV_FLOAT", uops)
4059+
self.assertNotIn("_GUARD_BINARY_OP_EXTEND", uops)
4060+
self.assertNotIn("_BINARY_OP_EXTEND", uops)
40594061

40604062
def test_float_remainder_speculative_guards_from_tracing(self):
40614063
# a, b are locals with no statically known type. Tracing records

Lib/test/test_opcache.py

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1448,6 +1448,44 @@ def binary_op_add_extend():
14481448
self.assert_specialized(binary_op_add_extend, "BINARY_OP_EXTEND")
14491449
self.assert_no_opcode(binary_op_add_extend, "BINARY_OP")
14501450

1451+
def float_true_divide(a, b):
1452+
return a / b
1453+
1454+
def float_inplace_true_divide(a, b):
1455+
a /= b
1456+
return a
1457+
1458+
for _ in range(_testinternalcapi.SPECIALIZATION_THRESHOLD):
1459+
self.assertEqual(float_true_divide(6.0, 3.0), 2.0)
1460+
self.assertEqual(float_inplace_true_divide(6.0, 3.0), 2.0)
1461+
1462+
self.assert_specialized(float_true_divide, "BINARY_OP_EXTEND")
1463+
self.assert_specialized(float_inplace_true_divide, "BINARY_OP_EXTEND")
1464+
with self.assertRaises(ZeroDivisionError) as cm:
1465+
float_true_divide(1.0, 0.0)
1466+
self.assertEqual(str(cm.exception), "division by zero")
1467+
with self.assertRaises(ZeroDivisionError) as cm:
1468+
float_inplace_true_divide(1.0, -0.0)
1469+
self.assertEqual(str(cm.exception), "division by zero")
1470+
nan = float_true_divide(float("nan"), 1.0)
1471+
self.assertNotEqual(nan, nan)
1472+
1473+
class FloatSubclass(float):
1474+
def __truediv__(self, other):
1475+
return "subclass truediv"
1476+
1477+
def __rtruediv__(self, other):
1478+
return "subclass reflected truediv"
1479+
1480+
self.assertEqual(
1481+
float_true_divide(FloatSubclass(6.0), 3.0),
1482+
"subclass truediv",
1483+
)
1484+
self.assertEqual(
1485+
float_true_divide(6.0, FloatSubclass(3.0)),
1486+
"subclass reflected truediv",
1487+
)
1488+
14511489
def binary_op_add_extend_sequences():
14521490
l1 = [1, 2]
14531491
l2 = [None]
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Specialize exact ``float`` true division in the tier 1 interpreter using
2+
``BINARY_OP_EXTEND``.

Python/bytecodes.c

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -901,8 +901,8 @@ dummy_func(
901901
INPUTS_DEAD();
902902
}
903903

904-
// Float true division --- not specialized at tier 1, emitted by the
905-
// tier 2 optimizer when both operands are known floats.
904+
// Float true division --- emitted by the tier 2 optimizer when both
905+
// operands are known floats.
906906
tier2 op(_BINARY_OP_TRUEDIV_FLOAT, (left, right -- res, l, r)) {
907907
PyObject *left_o = PyStackRef_AsPyObjectBorrow(left);
908908
PyObject *right_o = PyStackRef_AsPyObjectBorrow(right);

Python/optimizer_bytecodes.c

Lines changed: 49 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -517,7 +517,27 @@ dummy_func(void) {
517517
assert(d->lhs_type != NULL && d->rhs_type != NULL);
518518
bool lhs_known = sym_matches_type(left, d->lhs_type);
519519
bool rhs_known = sym_matches_type(right, d->rhs_type);
520-
if (lhs_known && rhs_known) {
520+
bool is_float_truediv = (
521+
(d->oparg == NB_TRUE_DIVIDE || d->oparg == NB_INPLACE_TRUE_DIVIDE) &&
522+
d->lhs_type == &PyFloat_Type &&
523+
d->rhs_type == &PyFloat_Type &&
524+
d->result_type == &PyFloat_Type &&
525+
d->result_unique
526+
);
527+
if (is_float_truediv) {
528+
if (!rhs_known) {
529+
ADD_OP(_GUARD_TOS_FLOAT, 0, 0);
530+
sym_set_type(right, &PyFloat_Type);
531+
}
532+
if (!lhs_known) {
533+
ADD_OP(_GUARD_NOS_FLOAT, 0, 0);
534+
sym_set_type(left, &PyFloat_Type);
535+
}
536+
if (lhs_known && rhs_known) {
537+
ADD_OP(_NOP, 0, 0);
538+
}
539+
}
540+
else if (lhs_known && rhs_known) {
521541
ADD_OP(_NOP, 0, 0);
522542
}
523543
else if (lhs_known) {
@@ -533,7 +553,34 @@ dummy_func(void) {
533553

534554
op(_BINARY_OP_EXTEND, (descr/4, left, right -- res, l, r)) {
535555
_PyBinaryOpSpecializationDescr *d = (_PyBinaryOpSpecializationDescr *)descr;
536-
if (d != NULL && d->result_type != NULL) {
556+
l = left;
557+
r = right;
558+
bool is_float_truediv = (
559+
d != NULL &&
560+
d->guard == NULL &&
561+
(d->oparg == NB_TRUE_DIVIDE || d->oparg == NB_INPLACE_TRUE_DIVIDE) &&
562+
d->lhs_type == &PyFloat_Type &&
563+
d->rhs_type == &PyFloat_Type &&
564+
d->result_type == &PyFloat_Type &&
565+
d->result_unique
566+
);
567+
if (is_float_truediv) {
568+
if (PyJitRef_IsUnique(left)) {
569+
ADD_OP(_BINARY_OP_TRUEDIV_FLOAT_INPLACE, 0, 0);
570+
l = sym_new_null(ctx);
571+
r = right;
572+
}
573+
else if (PyJitRef_IsUnique(right)) {
574+
ADD_OP(_BINARY_OP_TRUEDIV_FLOAT_INPLACE_RIGHT, 0, 0);
575+
l = left;
576+
r = sym_new_null(ctx);
577+
}
578+
else {
579+
ADD_OP(_BINARY_OP_TRUEDIV_FLOAT, 0, 0);
580+
}
581+
res = PyJitRef_MakeUnique(sym_new_type(ctx, &PyFloat_Type));
582+
}
583+
else if (d != NULL && d->result_type != NULL) {
537584
res = sym_new_type(ctx, d->result_type);
538585
if (d->result_unique) {
539586
res = PyJitRef_MakeUnique(res);
@@ -542,8 +589,6 @@ dummy_func(void) {
542589
else {
543590
res = sym_new_not_null(ctx);
544591
}
545-
l = left;
546-
r = right;
547592
}
548593

549594
op(_BINARY_OP_INPLACE_ADD_UNICODE, (left, right -- res)) {

Python/optimizer_cases.c.h

Lines changed: 49 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Python/specialize.c

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2179,6 +2179,19 @@ BITWISE_LONGS_ACTION(compactlongs_and, &)
21792179
BITWISE_LONGS_ACTION(compactlongs_xor, ^)
21802180
#undef BITWISE_LONGS_ACTION
21812181

2182+
/* float-float */
2183+
2184+
static PyObject *
2185+
floats_true_div(PyObject *lhs, PyObject *rhs)
2186+
{
2187+
double divisor = PyFloat_AS_DOUBLE(rhs);
2188+
if (divisor == 0.0) {
2189+
PyErr_SetString(PyExc_ZeroDivisionError, "division by zero");
2190+
return NULL;
2191+
}
2192+
return PyFloat_FromDouble(PyFloat_AS_DOUBLE(lhs) / divisor);
2193+
}
2194+
21822195
/* float-long */
21832196

21842197
static inline int
@@ -2259,6 +2272,10 @@ static _PyBinaryOpSpecializationDescr binaryop_extend_descrs[] = {
22592272
{NB_INPLACE_AND, compactlongs_guard, compactlongs_and, &PyLong_Type, 1, NULL, NULL},
22602273
{NB_INPLACE_XOR, compactlongs_guard, compactlongs_xor, &PyLong_Type, 1, NULL, NULL},
22612274

2275+
/* float-float true division */
2276+
{NB_TRUE_DIVIDE, NULL, floats_true_div, &PyFloat_Type, 1, &PyFloat_Type, &PyFloat_Type},
2277+
{NB_INPLACE_TRUE_DIVIDE, NULL, floats_true_div, &PyFloat_Type, 1, &PyFloat_Type, &PyFloat_Type},
2278+
22622279
/* float-long arithmetic: guards also check NaN and compactness. */
22632280
{NB_ADD, float_compactlong_guard, float_compactlong_add, &PyFloat_Type, 1, NULL, NULL},
22642281
{NB_SUBTRACT, float_compactlong_guard, float_compactlong_subtract, &PyFloat_Type, 1, NULL, NULL},

0 commit comments

Comments
 (0)