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
02ca84d
specialize concatenation of lists and tuples
eendebakpt Jan 17, 2025
27f4c56
📜🤖 Added by blurb_it.
blurb-it[bot] Jan 17, 2025
51d1b11
refactor for type information
eendebakpt Apr 5, 2026
e8263f9
add unique type propagation
eendebakpt Apr 5, 2026
77c1558
Merge branch 'main' into binary_op_list_list
eendebakpt Apr 6, 2026
fe63c59
special case for concatenation
eendebakpt Apr 6, 2026
f099585
fix
eendebakpt Apr 6, 2026
7f2c4a0
Merge branch 'main' into binary_op_list_list
eendebakpt Apr 7, 2026
d2dcb87
add asserts
eendebakpt Apr 7, 2026
6b0e5ed
review comments: reduce number of guards
eendebakpt Apr 9, 2026
d3f2d9d
review comments: reduce number of guards
eendebakpt Apr 9, 2026
49459c4
review comments: only guard on rhs or lhs if needed
eendebakpt Apr 9, 2026
72e8ab8
update tests
eendebakpt Apr 9, 2026
652286a
mark tier2 ops
eendebakpt Apr 10, 2026
6dc2e3c
Merge branch 'main' into binary_op_list_list
eendebakpt Apr 10, 2026
0b8dfee
Merge branch 'main' into binary_op_list_list
eendebakpt Apr 11, 2026
430248c
fix: pass descriptor to _GUARD_BINARY_OP_EXTEND_{LHS,RHS} in tier2 op…
eendebakpt Apr 11, 2026
a018c03
Regenerate optimizer_cases.c.h with descriptor operand fix
eendebakpt Apr 11, 2026
d6740d7
Merge branch 'main' into binary_op_list_list
eendebakpt Apr 11, 2026
538d072
fix ADD_OP
eendebakpt Apr 12, 2026
c390a03
Merge branch 'main' into binary_op_list_list
eendebakpt Apr 12, 2026
b2cb6b1
remove indirection
eendebakpt Apr 12, 2026
8d5e201
Merge remote-tracking branch 'origin/main' into binary_op_list_list
eendebakpt Apr 12, 2026
027fc39
Merge branch 'main' into binary_op_list_list
eendebakpt Apr 14, 2026
cdaf82f
Merge branch 'main' into binary_op_list_list
eendebakpt Apr 15, 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
special case for concatenation
  • Loading branch information
eendebakpt committed Apr 6, 2026
commit fe63c59490524e5e967fec380478765a946fd9f4
5 changes: 5 additions & 0 deletions Include/internal/pycore_code.h
Original file line number Diff line number Diff line change
Expand Up @@ -503,6 +503,11 @@ typedef struct {
aliased to either operand). Used by the tier 2 optimizer to enable
inplace follow-up ops. */
int result_unique;
/* Expected types of the left and right operands. Used by the tier 2
optimizer to eliminate _GUARD_BINARY_OP_EXTEND when the operand
types are already known. NULL means unknown/don't eliminate. */
PyTypeObject *lhs_type;
PyTypeObject *rhs_type;
} _PyBinaryOpSpecializationDescr;

/* Comparison bit masks. */
Expand Down
24 changes: 24 additions & 0 deletions Lib/test/test_capi/test_opt.py
Original file line number Diff line number Diff line change
Expand Up @@ -3878,6 +3878,30 @@ def testfunc(n):
self.assertIn("_UNPACK_SEQUENCE_TUPLE", uops)
self.assertNotIn("_GUARD_TOS_TUPLE", uops)

def test_binary_op_extend_guard_elimination(self):
# When both operands have known types (e.g., from a prior
# _BINARY_OP_EXTEND result), the _GUARD_BINARY_OP_EXTEND
# should be eliminated.
def testfunc(n):
a = [1, 2]
b = [3, 4]
total = 0
for _ in range(n):
c = a + b # first: guard stays, result type = list
d = c + c # second: both operands are list -> guard eliminated
total += d[0]
return total

res, ex = self._run_with_optimizer(testfunc, TIER2_THRESHOLD)
self.assertEqual(res, TIER2_THRESHOLD)
self.assertIsNotNone(ex)
uops = get_opnames(ex)
# Both list additions use _BINARY_OP_EXTEND
self.assertEqual(uops.count("_BINARY_OP_EXTEND"), 2)
# But the second guard is eliminated because both operands
# are known to be lists from the first _BINARY_OP_EXTEND.
self.assertEqual(uops.count("_GUARD_BINARY_OP_EXTEND"), 1)

def test_unary_invert_long_type(self):
def testfunc(n):
for _ in range(n):
Expand Down
6 changes: 3 additions & 3 deletions Objects/bytesobject.c
Original file line number Diff line number Diff line change
Expand Up @@ -1536,8 +1536,8 @@ bytes_length(PyObject *self)
return Py_SIZE(a);
}

/* This is also used by PyBytes_Concat() */
static PyObject *
/* This is also used by PyBytes_Concat() and BINARY_OP_EXTEND */
PyObject *
bytes_concat(PyObject *a, PyObject *b)
{
Py_buffer va, vb;
Expand Down Expand Up @@ -1581,7 +1581,7 @@ bytes_concat(PyObject *a, PyObject *b)
return result;
}

static PyObject *
PyObject *
bytes_repeat(PyObject *self, Py_ssize_t n)
{
PyBytesObject *a = _PyBytes_CAST(self);
Expand Down
4 changes: 2 additions & 2 deletions Objects/dictobject.c
Original file line number Diff line number Diff line change
Expand Up @@ -5041,7 +5041,7 @@ dict___sizeof___impl(PyDictObject *self)
return PyLong_FromSsize_t(_PyDict_SizeOf(self));
}

static PyObject *
PyObject *
dict_or(PyObject *self, PyObject *other)
{
if (!PyAnyDict_Check(self) || !PyAnyDict_Check(other)) {
Expand Down Expand Up @@ -5081,7 +5081,7 @@ frozendict_or(PyObject *self, PyObject *other)
}


static PyObject *
PyObject *
dict_ior(PyObject *self, PyObject *other)
{
if (dict_update_arg(self, other)) {
Expand Down
2 changes: 1 addition & 1 deletion Objects/tupleobject.c
Original file line number Diff line number Diff line change
Expand Up @@ -594,7 +594,7 @@ _PyTuple_Concat(PyObject *aa, PyObject *bb)
return (PyObject *)np;
}

static PyObject *
PyObject *
tuple_repeat(PyObject *self, Py_ssize_t n)
{
PyTupleObject *a = _PyTuple_CAST(self);
Expand Down
2 changes: 1 addition & 1 deletion Objects/unicodeobject.c
Original file line number Diff line number Diff line change
Expand Up @@ -12494,7 +12494,7 @@ unicode_rstrip_impl(PyObject *self, PyObject *chars)
}


static PyObject*
PyObject*
unicode_repeat(PyObject *str, Py_ssize_t len)
{
PyObject *u;
Expand Down
10 changes: 10 additions & 0 deletions Python/optimizer_bytecodes.c
Original file line number Diff line number Diff line change
Expand Up @@ -409,6 +409,16 @@ dummy_func(void) {
r = right;
}

op(_GUARD_BINARY_OP_EXTEND, (descr/4, left, right -- left, right)) {
Comment thread
eendebakpt marked this conversation as resolved.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is potential to optimize out the float/compact int and compact int/float guards.
Can be done in another PR.

_PyBinaryOpSpecializationDescr *d = (_PyBinaryOpSpecializationDescr *)descr;
if (d != NULL && d->lhs_type != NULL && d->rhs_type != NULL) {
if (sym_matches_type(left, d->lhs_type) &&
sym_matches_type(right, d->rhs_type)) {
REPLACE_OP(this_instr, _NOP, 0, 0);
}
}
}

op(_BINARY_OP_EXTEND, (descr/4, left, right -- res, l, r)) {
_PyBinaryOpSpecializationDescr *d = (_PyBinaryOpSpecializationDescr *)descr;
if (d != NULL && d->result_type != NULL) {
Expand Down
12 changes: 12 additions & 0 deletions Python/optimizer_cases.c.h

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

188 changes: 167 additions & 21 deletions Python/specialize.c
Original file line number Diff line number Diff line change
Expand Up @@ -2133,6 +2133,121 @@ tuple_tuple_add(PyObject *lhs, PyObject *rhs)
return _PyTuple_Concat(lhs, rhs);
}

/* sequence * int helpers: bypass PyNumber_Multiply dispatch overhead
by calling sq_repeat directly with PyLong_AsSsize_t. */

extern PyObject *unicode_repeat(PyObject *str, Py_ssize_t n);
extern PyObject *bytes_repeat(PyObject *self, Py_ssize_t n);
extern PyObject *bytes_concat(PyObject *a, PyObject *b);
extern PyObject *tuple_repeat(PyObject *self, Py_ssize_t n);
extern PyObject *dict_or(PyObject *self, PyObject *other);
extern PyObject *dict_ior(PyObject *self, PyObject *other);

static inline PyObject *
seq_int_multiply(PyObject *seq, PyObject *n,
ssizeargfunc repeat)
{
Py_ssize_t count = PyLong_AsSsize_t(n);
if (count == -1 && PyErr_Occurred()) {
return NULL;
}
return repeat(seq, count);
}

/* str-int and int-str */

static int
str_int_guard(PyObject *lhs, PyObject *rhs)
{
return PyUnicode_CheckExact(lhs) && PyLong_CheckExact(rhs);
}

static int
int_str_guard(PyObject *lhs, PyObject *rhs)
{
return PyLong_CheckExact(lhs) && PyUnicode_CheckExact(rhs);
}

static PyObject *
str_int_multiply(PyObject *lhs, PyObject *rhs)
{
return seq_int_multiply(lhs, rhs, unicode_repeat);
}

static PyObject *
int_str_multiply(PyObject *lhs, PyObject *rhs)
{
return seq_int_multiply(rhs, lhs, unicode_repeat);
}

/* bytes-bytes */

static int
bytes_bytes_guard(PyObject *lhs, PyObject *rhs)
{
return PyBytes_CheckExact(lhs) && PyBytes_CheckExact(rhs);
}

/* bytes-int and int-bytes */

static int
bytes_int_guard(PyObject *lhs, PyObject *rhs)
{
return PyBytes_CheckExact(lhs) && PyLong_CheckExact(rhs);
}

static int
int_bytes_guard(PyObject *lhs, PyObject *rhs)
{
return PyLong_CheckExact(lhs) && PyBytes_CheckExact(rhs);
}

static PyObject *
bytes_int_multiply(PyObject *lhs, PyObject *rhs)
{
return seq_int_multiply(lhs, rhs, bytes_repeat);
}

static PyObject *
int_bytes_multiply(PyObject *lhs, PyObject *rhs)
{
return seq_int_multiply(rhs, lhs, bytes_repeat);
}

/* tuple-int and int-tuple */

static int
tuple_int_guard(PyObject *lhs, PyObject *rhs)
{
return PyTuple_CheckExact(lhs) && PyLong_CheckExact(rhs);
}

static int
int_tuple_guard(PyObject *lhs, PyObject *rhs)
{
return PyLong_CheckExact(lhs) && PyTuple_CheckExact(rhs);
}

static PyObject *
tuple_int_multiply(PyObject *lhs, PyObject *rhs)
{
return seq_int_multiply(lhs, rhs, tuple_repeat);
}

static PyObject *
int_tuple_multiply(PyObject *lhs, PyObject *rhs)
{
return seq_int_multiply(rhs, lhs, tuple_repeat);
}

/* dict-dict */

static int
dict_dict_guard(PyObject *lhs, PyObject *rhs)
{
return PyDict_CheckExact(lhs) && PyDict_CheckExact(rhs);
}

static int
compactlongs_guard(PyObject *lhs, PyObject *rhs)
{
Expand Down Expand Up @@ -2223,32 +2338,63 @@ LONG_FLOAT_ACTION(compactlong_float_true_div, /)
#undef LONG_FLOAT_ACTION

static _PyBinaryOpSpecializationDescr binaryop_extend_descrs[] = {
/* long-long arithmetic */
{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, &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},

/* long-float arithmetic */
{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},
/* long-long arithmetic: guards also check _PyLong_IsCompact, so
type alone is not sufficient to eliminate the guard. */
{NB_OR, compactlongs_guard, compactlongs_or, &PyLong_Type, 1, NULL, NULL},
{NB_AND, compactlongs_guard, compactlongs_and, &PyLong_Type, 1, NULL, NULL},
{NB_XOR, compactlongs_guard, compactlongs_xor, &PyLong_Type, 1, NULL, NULL},
{NB_INPLACE_OR, compactlongs_guard, compactlongs_or, &PyLong_Type, 1, NULL, NULL},
{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-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},
{NB_TRUE_DIVIDE, nonzero_float_compactlong_guard, float_compactlong_true_div, &PyFloat_Type, 1, NULL, NULL},
{NB_MULTIPLY, float_compactlong_guard, float_compactlong_multiply, &PyFloat_Type, 1, NULL, NULL},

/* long-float arithmetic: guards also check NaN and compactness. */
{NB_ADD, compactlong_float_guard, compactlong_float_add, &PyFloat_Type, 1, NULL, NULL},
{NB_SUBTRACT, compactlong_float_guard, compactlong_float_subtract, &PyFloat_Type, 1, NULL, NULL},
{NB_TRUE_DIVIDE, nonzero_compactlong_float_guard, compactlong_float_true_div, &PyFloat_Type, 1, NULL, NULL},
{NB_MULTIPLY, compactlong_float_guard, compactlong_float_multiply, &PyFloat_Type, 1, NULL, NULL},

/* list-list concatenation: _PyList_Concat always allocates a new list */
{NB_ADD, list_list_guard, list_list_add, &PyList_Type, 1},
{NB_ADD, list_list_guard, list_list_add, &PyList_Type, 1, &PyList_Type, &PyList_Type},
/* tuple-tuple concatenation: _PyTuple_Concat has a zero-length shortcut
that can return one of the operands, so the result is not guaranteed
to be a freshly allocated object. */
{NB_ADD, tuple_tuple_guard, tuple_tuple_add, &PyTuple_Type, 0},
{NB_ADD, tuple_tuple_guard, tuple_tuple_add, &PyTuple_Type, 0, &PyTuple_Type, &PyTuple_Type},

/* str * int / int * str: call unicode_repeat directly.
unicode_repeat returns the original when n == 1. */
{NB_MULTIPLY, str_int_guard, str_int_multiply, &PyUnicode_Type, 0, &PyUnicode_Type, &PyLong_Type},
{NB_MULTIPLY, int_str_guard, int_str_multiply, &PyUnicode_Type, 0, &PyLong_Type, &PyUnicode_Type},
{NB_INPLACE_MULTIPLY, str_int_guard, str_int_multiply, &PyUnicode_Type, 0, &PyUnicode_Type, &PyLong_Type},
{NB_INPLACE_MULTIPLY, int_str_guard, int_str_multiply, &PyUnicode_Type, 0, &PyLong_Type, &PyUnicode_Type},

/* bytes + bytes: call bytes_concat directly. bytes_concat may return
an operand when one side is empty, so result is not always unique. */
{NB_ADD, bytes_bytes_guard, bytes_concat, &PyBytes_Type, 0, &PyBytes_Type, &PyBytes_Type},
{NB_INPLACE_ADD, bytes_bytes_guard, bytes_concat, &PyBytes_Type, 0, &PyBytes_Type, &PyBytes_Type},

/* bytes * int / int * bytes: call bytes_repeat directly.
bytes_repeat returns the original when n == 1. */
{NB_MULTIPLY, bytes_int_guard, bytes_int_multiply, &PyBytes_Type, 0, &PyBytes_Type, &PyLong_Type},
{NB_MULTIPLY, int_bytes_guard, int_bytes_multiply, &PyBytes_Type, 0, &PyLong_Type, &PyBytes_Type},
{NB_INPLACE_MULTIPLY, bytes_int_guard, bytes_int_multiply, &PyBytes_Type, 0, &PyBytes_Type, &PyLong_Type},
{NB_INPLACE_MULTIPLY, int_bytes_guard, int_bytes_multiply, &PyBytes_Type, 0, &PyLong_Type, &PyBytes_Type},

/* tuple * int / int * tuple: call tuple_repeat directly.
tuple_repeat returns the original when n == 1. */
{NB_MULTIPLY, tuple_int_guard, tuple_int_multiply, &PyTuple_Type, 0, &PyTuple_Type, &PyLong_Type},
{NB_MULTIPLY, int_tuple_guard, int_tuple_multiply, &PyTuple_Type, 0, &PyLong_Type, &PyTuple_Type},
{NB_INPLACE_MULTIPLY, tuple_int_guard, tuple_int_multiply, &PyTuple_Type, 0, &PyTuple_Type, &PyLong_Type},
{NB_INPLACE_MULTIPLY, int_tuple_guard, int_tuple_multiply, &PyTuple_Type, 0, &PyLong_Type, &PyTuple_Type},

/* dict | dict: call dict_or directly */
{NB_OR, dict_dict_guard, dict_or, &PyDict_Type, 1, &PyDict_Type, &PyDict_Type},
{NB_INPLACE_OR, dict_dict_guard, dict_ior, &PyDict_Type, 0, &PyDict_Type, &PyDict_Type},
};

static int
Expand Down
Loading