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
Prev Previous commit
Next Next commit
avoid speculative guards
  • Loading branch information
eendebakpt committed Mar 29, 2026
commit 53dd383c7da542915893d40b7d4f266e3e2b5039
60 changes: 30 additions & 30 deletions Lib/test/test_capi/test_opt.py
Original file line number Diff line number Diff line change
Expand Up @@ -3349,75 +3349,75 @@ def testfunc(args):
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
# (a + b) / (c + d): LHS is unique float from add, RHS is unique
# float from add. The division reuses the LHS in place.
def testfunc(args):
a, b, c, n = args
a, b, c, d, n = args
total = 0.0
for _ in range(n):
total += (a + b) / c
total += (a + b) / (c + d)
return total

res, ex = self._run_with_optimizer(testfunc, (2.0, 3.0, 4.0, TIER2_THRESHOLD))
res, ex = self._run_with_optimizer(testfunc, (2.0, 3.0, 1.0, 3.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 /
# x = c + d stores to a local (not unique when reloaded).
# (a + b) is unique. The division should use inplace on the RHS.
def testfunc(args):
a, b, c, n = args
a, b, c, d, n = args
total = 0.0
for _ in range(n):
total += c / (a + b)
x = c + d
total += x / (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)
res, ex = self._run_with_optimizer(testfunc, (2.0, 3.0, 4.0, 5.0, TIER2_THRESHOLD))
self.assertAlmostEqual(res, TIER2_THRESHOLD * (9.0 / 5.0))
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): the optimizer speculatively inserts float guards
# for both divisions, specializing them to _BINARY_OP_TRUEDIV_FLOAT.
# Their results are unique floats, so the + uses inplace.
# (a+b) / (c+d) - (e+f) / (g+h): all additions produce known-float
# results, so both divisions are specialized. The subtraction between
# the two division results should use inplace.
def testfunc(args):
a, b, c, d, n = args
a, b, n = args
total = 0.0
for _ in range(n):
total += (a / b) + (c / d)
x = (a + b) # type of x will specialize to float
total += x / x - x / x
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)
res, ex = self._run_with_optimizer(testfunc,
(2.0, 3.0, TIER2_THRESHOLD))
expected = TIER2_THRESHOLD * ((2.0 + 3.0) / (2.0 + 3.0) - (2.0 + 3.0) / (2.0 + 3.0))
self.assertAlmostEqual(res, expected)
self.assertIsNotNone(ex)
uops = get_opnames(ex)
# Both divisions are specialized with speculative guards
self.assertIn("_BINARY_OP_TRUEDIV_FLOAT", uops)
# The + uses inplace (a/b result is unique)
self.assertIn("_BINARY_OP_ADD_FLOAT_INPLACE", uops)
# The += uses inplace (+ result is unique)
self.assertIn("_BINARY_OP_ADD_FLOAT_INPLACE_RIGHT", uops)
self.assertIn("_BINARY_OP_TRUEDIV_FLOAT_INPLACE", uops)
self.assertIn("_BINARY_OP_SUBTRACT_FLOAT_INPLACE", uops)

def test_float_truediv_unique_result_enables_inplace(self):
# (a+b) / c / d: (a+b) is unique float, so the first / uses
# inplace. Its result is also unique, so the second / can use
# _BINARY_OP_TRUEDIV_FLOAT_INPLACE too.
# (a+b) / (c+d) / (e+f): chained divisions where each result
# is unique, enabling inplace for subsequent divisions.
def testfunc(args):
a, b, c, d, n = args
a, b, c, d, e, f, n = args
total = 0.0
for _ in range(n):
total += (a + b) / c / d
total += (a + b) / (c + d) / (e + f)
return total

res, ex = self._run_with_optimizer(testfunc, (2.0, 3.0, 4.0, 5.0, TIER2_THRESHOLD))
expected = TIER2_THRESHOLD * ((2.0 + 3.0) / 4.0 / 5.0)
res, ex = self._run_with_optimizer(testfunc,
(2.0, 3.0, 1.0, 1.0, 1.0, 1.0, TIER2_THRESHOLD))
expected = TIER2_THRESHOLD * ((2.0 + 3.0) / (1.0 + 1.0) / (1.0 + 1.0))
self.assertAlmostEqual(res, expected)
self.assertIsNotNone(ex)
uops = get_opnames(ex)
# Both divisions should use inplace (chained uniqueness)
self.assertIn("_BINARY_OP_TRUEDIV_FLOAT_INPLACE", uops)

def test_float_add_chain_both_unique(self):
Expand Down
17 changes: 1 addition & 16 deletions Python/optimizer_bytecodes.c
Original file line number Diff line number Diff line change
Expand Up @@ -254,22 +254,8 @@ dummy_func(void) {
bool rhs_int = sym_matches_type(rhs, &PyLong_Type);
bool lhs_float = sym_matches_type(lhs, &PyFloat_Type);
bool rhs_float = sym_matches_type(rhs, &PyFloat_Type);
// Specialize float/float true division in tier 2.
// Speculatively insert guards for operands not yet known to be
// float. Skip if both are known int (int/int handled below) or
// if either is a known non-int/float type (Fraction, Decimal, etc.)
if ((oparg == NB_TRUE_DIVIDE || oparg == NB_INPLACE_TRUE_DIVIDE)
&& !(lhs_int && rhs_int)
&& !(!lhs_int && !lhs_float && sym_has_type(lhs))
&& !(!rhs_int && !rhs_float && sym_has_type(rhs))) {
if (!rhs_float) {
ADD_OP(_GUARD_TOS_FLOAT, 0, 0);
sym_set_type(rhs, &PyFloat_Type);
}
if (!lhs_float) {
ADD_OP(_GUARD_NOS_FLOAT, 0, 0);
sym_set_type(lhs, &PyFloat_Type);
}
&& lhs_float && rhs_float) {
if (PyJitRef_IsUnique(lhs)) {
ADD_OP(_BINARY_OP_TRUEDIV_FLOAT_INPLACE, 0, 0);
l = sym_new_null(ctx);
Expand All @@ -289,7 +275,6 @@ dummy_func(void) {
}
else if ((oparg == NB_TRUE_DIVIDE || oparg == NB_INPLACE_TRUE_DIVIDE)
&& (lhs_int || lhs_float) && (rhs_int || rhs_float)) {
// int / int always returns float. No guards needed.
res = sym_new_type(ctx, &PyFloat_Type);
Comment thread
eendebakpt marked this conversation as resolved.
Outdated
}
else if (!((lhs_int || lhs_float) && (rhs_int || rhs_float))) {
Expand Down
12 changes: 1 addition & 11 deletions Python/optimizer_cases.c.h

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