Skip to content
Closed
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
8 changes: 7 additions & 1 deletion Include/cpython/code.h
Original file line number Diff line number Diff line change
Expand Up @@ -160,8 +160,14 @@ PyAPI_FUNC(int) _PyCode_CheckLineNumber(int lasti, PyCodeAddressRange *bounds);
*
* Return (type(obj), obj, ...): a tuple with variable size (at least 2 items)
* depending on the type and the value. The type is the first item to not
* compare bytes and str which can raise a BytesWarning exception. */
* compare bytes and str which can raise a BytesWarning exception.
*
* Note: For slice objects, the tuple will consist from type(obj), and values
* of all fields (lower, upper, step) as integers so that it can be reconsturected
* using _PyCode_ConstantValue. */

PyAPI_FUNC(PyObject*) _PyCode_ConstantKey(PyObject *obj);
PyAPI_FUNC(PyObject*) _PyCode_ConstantValue(PyObject *obj);

PyAPI_FUNC(PyObject*) PyCode_Optimize(PyObject *code, PyObject* consts,
PyObject *names, PyObject *lnotab);
Expand Down
3 changes: 2 additions & 1 deletion Lib/importlib/_bootstrap_external.py
Original file line number Diff line number Diff line change
Expand Up @@ -353,6 +353,7 @@ def _write_atomic(path, data, mode=0o666):
# Python 3.10b1 3438 Safer line number table handling.
# Python 3.10b1 3439 (Add ROT_N)
# Python 3.11a1 3450 Use exception table for unwinding ("zero cost" exception handling)
# Python 3.11a1 3451 Constant folding for slice objects

#
# MAGIC must change whenever the bytecode emitted by the compiler may no
Expand All @@ -362,7 +363,7 @@ def _write_atomic(path, data, mode=0o666):
# Whenever MAGIC_NUMBER is changed, the ranges in the magic_values array
# in PC/launcher.c must also be updated.

MAGIC_NUMBER = (3450).to_bytes(2, 'little') + b'\r\n'
MAGIC_NUMBER = (3451).to_bytes(2, 'little') + b'\r\n'
_RAW_MAGIC_NUMBER = int.from_bytes(MAGIC_NUMBER, 'little') # For import.c

_PYCACHE = '__pycache__'
Expand Down
5 changes: 5 additions & 0 deletions Lib/test/test_marshal.py
Original file line number Diff line number Diff line change
Expand Up @@ -425,6 +425,11 @@ def testBytes(self):
self.helper(bytesobj)
self.helper3(bytesobj)

def testSlice(self):
slice_obj = slice(1, None, -2)
self.helper(slice_obj)
self.helper3(slice_obj)

def testList(self):
for obj in self.keys:
listobj = [obj, obj]
Expand Down
24 changes: 24 additions & 0 deletions Lib/test/test_peepholer.py
Original file line number Diff line number Diff line change
Expand Up @@ -494,6 +494,30 @@ def genexpr():
return (y for x in a for y in [f(x)])
self.assertEqual(count_instr_recursively(genexpr, 'FOR_ITER'), 1)

def test_slice_folding(self):
def f():
a[1:1:1], a[::1], a[1::], a[1::1],
a[::-1], a[-1::], a[:], a[::], a[:1]

consts = f.__code__.co_consts
self.assertIn(slice(1, 1, 1), consts)
self.assertIn(slice(1, None, 1), consts)
self.assertIn(slice(1, None, None), consts)
self.assertIn(slice(None, None, 1), consts)
self.assertIn(slice(None, 1, None), consts)
self.assertIn(slice(None, None, -1), consts)
self.assertIn(slice(-1, None, None), consts)
self.assertIn(slice(None, None, None), consts)

def f(a):
return (
a[:][1:] + a[::][-1:] + a[::2]
+ a[:][1:] + a[::][-1:] + a[::2]
)

consts = f.__code__.co_consts
# 4 different slices + 1 None
self.assertEqual(len(consts), 5)

class TestBuglets(unittest.TestCase):

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
The AST optimizer now folds constant slices and stores them in the code
objects. Patch by Batuhan Taskaya.
78 changes: 77 additions & 1 deletion Objects/codeobject.c
Original file line number Diff line number Diff line change
Expand Up @@ -907,6 +907,15 @@ _PyCode_ConstantKey(PyObject *op)
key = PyTuple_Pack(2, set, op);
Py_DECREF(set);
return key;
} else if (PySlice_Check(op)) {
PySliceObject *slice = (PySliceObject *)op;
key = PyTuple_Pack(
4,
(PyObject *)Py_TYPE(op),
slice->start,
slice->stop,
slice->step
);
}
else {
/* for other types, use the object identifier as a unique identifier
Expand All @@ -921,6 +930,29 @@ _PyCode_ConstantKey(PyObject *op)
return key;
}

PyObject *
_PyCode_ConstantValue(PyObject *obj)
{
assert(PyTuple_CheckExact(obj));
assert(PyTuple_GET_SIZE(obj) > 1);
PyObject *possible_identifier = PyTuple_GET_ITEM(obj, 0);
if (PyType_CheckExact(possible_identifier)) {
PyTypeObject *type = (PyTypeObject *)possible_identifier;

if (type == &PySlice_Type) {
assert(PyTuple_GET_SIZE(obj) == 4);
return PySlice_New(
PyTuple_GET_ITEM(obj, 1),
PyTuple_GET_ITEM(obj, 2),
PyTuple_GET_ITEM(obj, 3)
);
}
}
PyObject *constant = PyTuple_GET_ITEM(obj, 1);
Py_INCREF(constant);
return constant;
}

static PyObject *
code_richcompare(PyObject *self, PyObject *other, int op)
{
Expand Down Expand Up @@ -997,6 +1029,50 @@ code_richcompare(PyObject *self, PyObject *other, int op)
return res;
}

static Py_hash_t
code_slice_const_hash(PySliceObject *r)
{
PyObject *slice_tuple = PyTuple_Pack(3, r->start, r->stop, r->step);
if (slice_tuple == NULL) {
return -1;
}
Py_hash_t hash = PyObject_Hash(slice_tuple);
Py_DECREF(slice_tuple);
return hash;
}

static Py_hash_t
code_const_hash(PyObject *consts)
{
assert(PyTuple_CheckExact(consts));
Py_ssize_t i, length = PyTuple_GET_SIZE(consts);
if (length == -1) {
return -1;
} else if (length == 0) {
// If it is an empty tuple, use
// directly its hash!
return PyObject_Hash(consts);
}

PyObject *constant;
Py_hash_t temp_hash, h = 0;
for (i = 0; i < length; i++) {
constant = PyTuple_GET_ITEM(consts, i);
if (PySlice_Check(constant)) {
temp_hash = code_slice_const_hash((PySliceObject *)constant);
} else {
temp_hash = PyObject_Hash(constant);
}

if (temp_hash == -1) {
return -1;
}
h = h ^ temp_hash;
}

return h;
}

static Py_hash_t
code_hash(PyCodeObject *co)
{
Expand All @@ -1005,7 +1081,7 @@ code_hash(PyCodeObject *co)
if (h0 == -1) return -1;
h1 = PyObject_Hash(co->co_code);
if (h1 == -1) return -1;
h2 = PyObject_Hash(co->co_consts);
h2 = code_const_hash(co->co_consts);
if (h2 == -1) return -1;
h3 = PyObject_Hash(co->co_names);
if (h3 == -1) return -1;
Expand Down
28 changes: 28 additions & 0 deletions Python/ast_opt.c
Original file line number Diff line number Diff line change
Expand Up @@ -540,6 +540,28 @@ fold_tuple(expr_ty node, PyArena *arena, _PyASTOptimizeState *state)
return make_const(node, newval, arena);
}

static int
fold_constant_slice(expr_ty node, PyArena *arena, _PyASTOptimizeState *state)
{
PyObject *args[3];
expr_ty slices[3] = {
node->v.Slice.lower,
node->v.Slice.upper,
node->v.Slice.step
};
for (int i = 0; i < 3; i++) {
expr_ty slice = slices[i];
if (slice == NULL) {
args[i] = NULL;
} else if (slice->kind == Constant_kind && PyLong_CheckExact(slice->v.Constant.value)) {
args[i] = slice->v.Constant.value;
} else {
return 1;
}
}
Comment thread
isidentical marked this conversation as resolved.
Outdated
return make_const(node, PySlice_New(args[0], args[1], args[2]), arena);
}

static int
fold_subscr(expr_ty node, PyArena *arena, _PyASTOptimizeState *state)
{
Expand All @@ -548,6 +570,12 @@ fold_subscr(expr_ty node, PyArena *arena, _PyASTOptimizeState *state)

arg = node->v.Subscript.value;
idx = node->v.Subscript.slice;
if (idx->kind == Slice_kind &&
!fold_constant_slice(idx, arena, state))
{
return 0;
}

if (node->v.Subscript.ctx != Load ||
arg->kind != Constant_kind ||
idx->kind != Constant_kind)
Expand Down
26 changes: 22 additions & 4 deletions Python/compile.c
Original file line number Diff line number Diff line change
Expand Up @@ -7054,9 +7054,13 @@ consts_dict_keys_inorder(PyObject *dict)
* (see compiler_add_o and _PyCode_ConstantKey). In that case
* the object we want is always second. */
if (PyTuple_CheckExact(k)) {
k = PyTuple_GET_ITEM(k, 1);
k = _PyCode_ConstantValue(k);
if (k == NULL) {
return NULL;
}
} else {
Py_INCREF(k);
}
Py_INCREF(k);
assert(i < size);
assert(i >= 0);
PyList_SET_ITEM(consts, i, k);
Expand Down Expand Up @@ -7107,13 +7111,27 @@ merge_const_one(struct compiler *c, PyObject **obj)
return 0;
}

/* Since tuples and frozensets are the only containers that are
allowed to be among code object's constants, we will use the
expanded form for them since not every element might not be
hashable. */
PyObject *c_key;
if (PyTuple_CheckExact(key)) {
c_key = PyTuple_GET_ITEM(key, 0);
if (!PyTuple_CheckExact(c_key) && !PyFrozenSet_CheckExact(c_key)) {
c_key = key;
}
} else {
c_key = key;
}

// t is borrowed reference
PyObject *t = PyDict_SetDefault(c->c_const_cache, key, key);
PyObject *t = PyDict_SetDefault(c->c_const_cache, c_key, key);
Py_DECREF(key);
if (t == NULL) {
return 0;
}
if (t == key) { // obj is new constant.
if (t == c_key) { // obj is new constant.
return 1;
}

Expand Down
Loading