Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
c846269
gh-146306: Specialize float/float true division in tier 2 optimizer
eendebakpt Mar 24, 2026
97889f5
Mark results of float-producing _BINARY_OP as unique
eendebakpt Mar 24, 2026
67869e4
Fix truediv type propagation for non-numeric types
eendebakpt Mar 25, 2026
5c4e3bf
add guards
eendebakpt Mar 25, 2026
228bfa9
news entry
eendebakpt Mar 25, 2026
8bf12bf
Merge branch 'main' into jit_float_truediv
eendebakpt Mar 25, 2026
46c241e
Merge remote-tracking branch 'upstream/main' into pr/146397
Fidget-Spinner Mar 29, 2026
53dd383
avoid speculative guards
eendebakpt Mar 29, 2026
44ee7f0
fix test
eendebakpt Mar 29, 2026
e302112
Merge branch 'main' into jit_float_truediv
eendebakpt Apr 3, 2026
d2c03bc
Regenerate pycore_uop_ids.h and pycore_uop_metadata.h with truediv fl…
eendebakpt Apr 3, 2026
dba0bb3
Merge branch 'main' into jit_float_truediv
eendebakpt Apr 3, 2026
8615577
Merge branch 'main' into jit_float_truediv
eendebakpt Apr 4, 2026
e715dac
review comments
eendebakpt Apr 5, 2026
925841c
Merge branch 'main' into jit_float_truediv
eendebakpt Apr 5, 2026
43f4987
mark more returns as unique
eendebakpt Apr 6, 2026
a49018e
Merge branch 'main' into jit_float_truediv
eendebakpt Apr 6, 2026
9cab900
mark more returns as unique
eendebakpt Apr 6, 2026
b432e1a
Merge branch 'main' into jit_float_truediv
eendebakpt Apr 11, 2026
0c89654
Merge branch 'main' into jit_float_truediv
eendebakpt Apr 12, 2026
fa97ab3
Merge branch 'main' into jit_float_truediv
eendebakpt Apr 14, 2026
3ba0c85
add back recording ops and guards
eendebakpt Apr 14, 2026
e7fdb04
simplify
eendebakpt Apr 14, 2026
44666f7
Merge remote-tracking branch 'upstream/main' into pr/146397
Fidget-Spinner Apr 14, 2026
9a8c143
Merge branch 'jit_float_truediv' of github.com:eendebakpt/cpython int…
Fidget-Spinner Apr 14, 2026
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
Next Next commit
gh-146306: Specialize float/float true division in tier 2 optimizer
Add inplace float true division ops that the tier 2 optimizer emits
when at least one operand is a known float:

- _BINARY_OP_TRUEDIV_FLOAT_INPLACE (unique LHS)
- _BINARY_OP_TRUEDIV_FLOAT_INPLACE_RIGHT (unique RHS)

The optimizer inserts _GUARD_TOS_FLOAT / _GUARD_NOS_FLOAT for
operands not yet known to be float, enabling specialization in
expressions like `(a + b) / c`.

Also marks the result of all NB_TRUE_DIVIDE operations as unique
float in the abstract interpreter, enabling downstream inplace ops
even for generic `a / b` (the `+=` can reuse the division result).

Speeds up chain division patterns by ~2.3x and simple `total += a/b`
by ~1.5x.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
  • Loading branch information
eendebakpt and claude committed Mar 24, 2026
commit c846269e1038a4d3cb9a4b88754db9a1cfa0b711
2,440 changes: 1,224 additions & 1,216 deletions Include/internal/pycore_uop_ids.h

Large diffs are not rendered by default.

38 changes: 38 additions & 0 deletions Include/internal/pycore_uop_metadata.h

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

70 changes: 70 additions & 0 deletions Lib/test/test_capi/test_opt.py
Original file line number Diff line number Diff line change
Expand Up @@ -3237,6 +3237,76 @@ def testfunc(args):
uops = get_opnames(ex)
self.assertNotIn("_UNARY_NEGATIVE_FLOAT_INPLACE", uops)

def test_float_truediv_inplace_unique_lhs(self):
# (a + b) produces a unique float; dividing by c reuses it
def testfunc(args):
a, b, c, n = args
total = 0.0
for _ in range(n):
total += (a + b) / c
return total

res, ex = self._run_with_optimizer(testfunc, (2.0, 3.0, 4.0, TIER2_THRESHOLD))
self.assertAlmostEqual(res, TIER2_THRESHOLD * 1.25)
self.assertIsNotNone(ex)
uops = get_opnames(ex)
self.assertIn("_BINARY_OP_TRUEDIV_FLOAT_INPLACE", uops)

def test_float_truediv_inplace_unique_rhs(self):
# (a + b) produces a unique float on the right side of /
def testfunc(args):
a, b, c, n = args
total = 0.0
for _ in range(n):
total += c / (a + b)
return total

res, ex = self._run_with_optimizer(testfunc, (2.0, 3.0, 4.0, TIER2_THRESHOLD))
self.assertAlmostEqual(res, TIER2_THRESHOLD * 0.8)
self.assertIsNotNone(ex)
uops = get_opnames(ex)
self.assertIn("_BINARY_OP_TRUEDIV_FLOAT_INPLACE_RIGHT", uops)

def test_float_truediv_type_propagation(self):
# (a/b) + (c/d): inner divisions are generic _BINARY_OP but
# type propagation marks their results as float, so the +
# is specialized and the += uses inplace on the unique result
def testfunc(args):
a, b, c, d, n = args
total = 0.0
for _ in range(n):
total += (a / b) + (c / d)
return total

res, ex = self._run_with_optimizer(testfunc, (10.0, 3.0, 4.0, 5.0, TIER2_THRESHOLD))
expected = TIER2_THRESHOLD * (10.0 / 3.0 + 4.0 / 5.0)
self.assertAlmostEqual(res, expected)
self.assertIsNotNone(ex)
uops = get_opnames(ex)
# The + between the two division results should use inplace
# (the a/b result is unique from type propagation)
self.assertIn("_BINARY_OP_ADD_FLOAT_INPLACE", uops)
# The += should also use inplace (the + result is unique)
self.assertIn("_BINARY_OP_ADD_FLOAT_INPLACE_RIGHT", uops)

def test_float_truediv_unique_result_enables_inplace_add(self):
# a / b: the generic division result is marked as unique float
# by type propagation, so total += (a / b) uses inplace add
def testfunc(args):
a, b, n = args
total = 0.0
for _ in range(n):
total += a / b
return total

res, ex = self._run_with_optimizer(testfunc, (10.0, 3.0, TIER2_THRESHOLD))
expected = TIER2_THRESHOLD * (10.0 / 3.0)
self.assertAlmostEqual(res, expected)
self.assertIsNotNone(ex)
uops = get_opnames(ex)
# The += uses inplace because the division result is unique
self.assertIn("_BINARY_OP_ADD_FLOAT_INPLACE_RIGHT", uops)

def test_load_attr_instance_value(self):
def testfunc(n):
class C():
Expand Down
22 changes: 22 additions & 0 deletions Python/bytecodes.c
Original file line number Diff line number Diff line change
Expand Up @@ -838,6 +838,28 @@ dummy_func(
INPUTS_DEAD();
}

tier2 op(_BINARY_OP_TRUEDIV_FLOAT_INPLACE, (left, right -- res, l, r)) {
FLOAT_INPLACE_DIVOP(left, right, left);
if (_divop_err) {
ERROR_NO_POP();
}
res = left;
l = PyStackRef_NULL;
r = right;
INPUTS_DEAD();
}

tier2 op(_BINARY_OP_TRUEDIV_FLOAT_INPLACE_RIGHT, (left, right -- res, l, r)) {
FLOAT_INPLACE_DIVOP(left, right, right);
if (_divop_err) {
ERROR_NO_POP();
}
res = right;
l = left;
r = PyStackRef_NULL;
INPUTS_DEAD();
}

pure op(_BINARY_OP_ADD_UNICODE, (left, right -- res, l, r)) {
PyObject *left_o = PyStackRef_AsPyObjectBorrow(left);
PyObject *right_o = PyStackRef_AsPyObjectBorrow(right);
Expand Down
24 changes: 24 additions & 0 deletions Python/ceval_macros.h
Original file line number Diff line number Diff line change
Expand Up @@ -562,3 +562,27 @@ gen_try_set_executing(PyGenObject *gen)
((PyFloatObject *)PyStackRef_AsPyObjectBorrow(TARGET)) \
->ob_fval = _dres; \
} while (0)

// Inplace float true division. Sets _divop_err to 1 on zero division.
// Caller must check _divop_err and call ERROR_NO_POP() if set.
#define FLOAT_INPLACE_DIVOP(left, right, TARGET) \
int _divop_err = 0; \
do { \
PyObject *left_o = PyStackRef_AsPyObjectBorrow(left); \
PyObject *right_o = PyStackRef_AsPyObjectBorrow(right); \
assert(PyFloat_CheckExact(left_o)); \
assert(PyFloat_CheckExact(right_o)); \
assert(_PyObject_IsUniquelyReferenced( \
PyStackRef_AsPyObjectBorrow(TARGET))); \
STAT_INC(BINARY_OP, hit); \
double _divisor = ((PyFloatObject *)right_o)->ob_fval; \
if (_divisor == 0.0) { \
PyErr_SetString(PyExc_ZeroDivisionError, \
"float division by zero"); \
_divop_err = 1; \
break; \
} \
double _dres = ((PyFloatObject *)left_o)->ob_fval / _divisor; \
((PyFloatObject *)PyStackRef_AsPyObjectBorrow(TARGET)) \
->ob_fval = _dres; \
} while (0)
184 changes: 184 additions & 0 deletions Python/executor_cases.c.h

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

Loading
Loading