Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 7 additions & 5 deletions Lib/test/test_capi/test_opt.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down
38 changes: 38 additions & 0 deletions Lib/test/test_opcache.py
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Specialize exact ``float`` true division in the tier 1 interpreter using
``BINARY_OP_EXTEND``.
4 changes: 2 additions & 2 deletions Python/bytecodes.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
53 changes: 49 additions & 4 deletions Python/optimizer_bytecodes.c
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand All @@ -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);
Expand All @@ -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)) {
Expand Down
53 changes: 49 additions & 4 deletions Python/optimizer_cases.c.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

17 changes: 17 additions & 0 deletions Python/specialize.c
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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},
Expand Down
Loading