https://github.com/python/cpython/commit/efda60e2ece11beda204f89de8cf8ecd1e66fde5
commit: efda60e2ece11beda204f89de8cf8ecd1e66fde5
branch: main
author: Pieter Eendebak <[email protected]>
committer: Fidget-Spinner <[email protected]>
date: 2026-04-06T20:52:42+08:00
summary:
gh-100239: Propagate type info through _BINARY_OP_EXTEND in tier 2 (GH-148146)
files:
A
Misc/NEWS.d/next/Core_and_Builtins/2026-04-06-00-00-00.gh-issue-100239.binopxt.rst
M Include/internal/pycore_code.h
M Lib/test/test_capi/test_opt.py
M Python/optimizer_bytecodes.c
M Python/optimizer_cases.c.h
M Python/specialize.c
diff --git a/Include/internal/pycore_code.h b/Include/internal/pycore_code.h
index 376e68a4c8773c..fe8d0a54f2af1a 100644
--- a/Include/internal/pycore_code.h
+++ b/Include/internal/pycore_code.h
@@ -496,6 +496,13 @@ typedef struct {
int oparg;
binaryopguardfunc guard;
binaryopactionfunc action;
+ /* Static type of the result, or NULL if unknown. Used by the tier 2
+ optimizer to propagate type information through _BINARY_OP_EXTEND. */
+ PyTypeObject *result_type;
+ /* Nonzero iff `action` always returns a freshly allocated object (not
+ aliased to either operand). Used by the tier 2 optimizer to enable
+ inplace follow-up ops. */
+ int result_unique;
} _PyBinaryOpSpecializationDescr;
/* Comparison bit masks. */
diff --git a/Lib/test/test_capi/test_opt.py b/Lib/test/test_capi/test_opt.py
index 56f90194b480a1..b31c9f68d01bec 100644
--- a/Lib/test/test_capi/test_opt.py
+++ b/Lib/test/test_capi/test_opt.py
@@ -3813,6 +3813,29 @@ def f(n):
self.assertIn("_UNPACK_SEQUENCE_TWO_TUPLE", uops)
self.assertNotIn("_GUARD_TOS_TUPLE", uops)
+ def test_binary_op_extend_float_result_enables_inplace_multiply(self):
+ # (2 + x) * y with x, y floats: `2 + x` goes through _BINARY_OP_EXTEND
+ # (int + float). The result_type/result_unique info should let the
+ # subsequent float multiply use the inplace variant.
+ def testfunc(n):
+ x = 3.5
+ y = 2.0
+ res = 0.0
+ for _ in range(n):
+ res = (2 + x) * y
+ return res
+
+ res, ex = self._run_with_optimizer(testfunc, TIER2_THRESHOLD)
+ self.assertEqual(res, 11.0)
+ self.assertIsNotNone(ex)
+ uops = get_opnames(ex)
+ self.assertIn("_BINARY_OP_EXTEND", uops)
+ self.assertIn("_BINARY_OP_MULTIPLY_FLOAT_INPLACE", uops)
+ self.assertNotIn("_BINARY_OP_MULTIPLY_FLOAT", uops)
+ # NOS guard on the multiply is eliminated because _BINARY_OP_EXTEND
+ # propagates PyFloat_Type.
+ self.assertNotIn("_GUARD_NOS_FLOAT", uops)
+
def test_unary_invert_long_type(self):
def testfunc(n):
for _ in range(n):
diff --git
a/Misc/NEWS.d/next/Core_and_Builtins/2026-04-06-00-00-00.gh-issue-100239.binopxt.rst
b/Misc/NEWS.d/next/Core_and_Builtins/2026-04-06-00-00-00.gh-issue-100239.binopxt.rst
new file mode 100644
index 00000000000000..9eccef3ef9d342
--- /dev/null
+++
b/Misc/NEWS.d/next/Core_and_Builtins/2026-04-06-00-00-00.gh-issue-100239.binopxt.rst
@@ -0,0 +1,3 @@
+Propagate result type and uniqueness information through
+``_BINARY_OP_EXTEND`` in the tier 2 optimizer, enabling elimination of
+downstream type guards and selection of inplace float operations.
diff --git a/Python/optimizer_bytecodes.c b/Python/optimizer_bytecodes.c
index b8148ef57ede0c..58b50707e55cee 100644
--- a/Python/optimizer_bytecodes.c
+++ b/Python/optimizer_bytecodes.c
@@ -410,8 +410,16 @@ dummy_func(void) {
}
op(_BINARY_OP_EXTEND, (descr/4, left, right -- res, l, r)) {
- (void)descr;
- res = sym_new_not_null(ctx);
+ _PyBinaryOpSpecializationDescr *d = (_PyBinaryOpSpecializationDescr
*)descr;
+ if (d != NULL && d->result_type != NULL) {
+ res = sym_new_type(ctx, d->result_type);
+ if (d->result_unique) {
+ res = PyJitRef_MakeUnique(res);
+ }
+ }
+ else {
+ res = sym_new_not_null(ctx);
+ }
l = left;
r = right;
}
diff --git a/Python/optimizer_cases.c.h b/Python/optimizer_cases.c.h
index a15b5ae1d13d3b..891887301119d7 100644
--- a/Python/optimizer_cases.c.h
+++ b/Python/optimizer_cases.c.h
@@ -1168,8 +1168,16 @@
right = stack_pointer[-1];
left = stack_pointer[-2];
PyObject *descr = (PyObject *)this_instr->operand0;
- (void)descr;
- res = sym_new_not_null(ctx);
+ _PyBinaryOpSpecializationDescr *d =
(_PyBinaryOpSpecializationDescr *)descr;
+ if (d != NULL && d->result_type != NULL) {
+ res = sym_new_type(ctx, d->result_type);
+ if (d->result_unique) {
+ res = PyJitRef_MakeUnique(res);
+ }
+ }
+ else {
+ res = sym_new_not_null(ctx);
+ }
l = left;
r = right;
CHECK_STACK_BOUNDS(1);
diff --git a/Python/specialize.c b/Python/specialize.c
index 09ec25767a4c3f..0fe225dcbb6b5f 100644
--- a/Python/specialize.c
+++ b/Python/specialize.c
@@ -2195,24 +2195,24 @@ LONG_FLOAT_ACTION(compactlong_float_true_div, /)
static _PyBinaryOpSpecializationDescr binaryop_extend_descrs[] = {
/* long-long arithmetic */
- {NB_OR, compactlongs_guard, compactlongs_or},
- {NB_AND, compactlongs_guard, compactlongs_and},
- {NB_XOR, compactlongs_guard, compactlongs_xor},
- {NB_INPLACE_OR, compactlongs_guard, compactlongs_or},
- {NB_INPLACE_AND, compactlongs_guard, compactlongs_and},
- {NB_INPLACE_XOR, compactlongs_guard, compactlongs_xor},
+ {NB_OR, compactlongs_guard, compactlongs_or, &PyLong_Type, 1},
+ {NB_AND, compactlongs_guard, compactlongs_and, &PyLong_Type, 1},
+ {NB_XOR, compactlongs_guard, compactlongs_xor, &PyLong_Type, 1},
+ {NB_INPLACE_OR, compactlongs_guard, compactlongs_or, &PyLong_Type, 1},
+ {NB_INPLACE_AND, compactlongs_guard, compactlongs_and, &PyLong_Type, 1},
+ {NB_INPLACE_XOR, compactlongs_guard, compactlongs_xor, &PyLong_Type, 1},
/* float-long arithemetic */
- {NB_ADD, float_compactlong_guard, float_compactlong_add},
- {NB_SUBTRACT, float_compactlong_guard, float_compactlong_subtract},
- {NB_TRUE_DIVIDE, nonzero_float_compactlong_guard,
float_compactlong_true_div},
- {NB_MULTIPLY, float_compactlong_guard, float_compactlong_multiply},
+ {NB_ADD, float_compactlong_guard, float_compactlong_add, &PyFloat_Type, 1},
+ {NB_SUBTRACT, float_compactlong_guard, float_compactlong_subtract,
&PyFloat_Type, 1},
+ {NB_TRUE_DIVIDE, nonzero_float_compactlong_guard,
float_compactlong_true_div, &PyFloat_Type, 1},
+ {NB_MULTIPLY, float_compactlong_guard, float_compactlong_multiply,
&PyFloat_Type, 1},
/* float-float arithmetic */
- {NB_ADD, compactlong_float_guard, compactlong_float_add},
- {NB_SUBTRACT, compactlong_float_guard, compactlong_float_subtract},
- {NB_TRUE_DIVIDE, nonzero_compactlong_float_guard,
compactlong_float_true_div},
- {NB_MULTIPLY, compactlong_float_guard, compactlong_float_multiply},
+ {NB_ADD, compactlong_float_guard, compactlong_float_add, &PyFloat_Type, 1},
+ {NB_SUBTRACT, compactlong_float_guard, compactlong_float_subtract,
&PyFloat_Type, 1},
+ {NB_TRUE_DIVIDE, nonzero_compactlong_float_guard,
compactlong_float_true_div, &PyFloat_Type, 1},
+ {NB_MULTIPLY, compactlong_float_guard, compactlong_float_multiply,
&PyFloat_Type, 1},
};
static int
_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3//lists/python-checkins.python.org
Member address: [email protected]