mirror of
https://github.com/python/cpython.git
synced 2025-08-04 17:08:35 +00:00
gh-126835: Make CFG optimizer skip over NOP's when looking for const sequence construction (#129703)
Co-authored-by: Irit Katriel <1055913+iritkatriel@users.noreply.github.com>
This commit is contained in:
parent
8f9c6fa077
commit
91d9544112
2 changed files with 291 additions and 74 deletions
|
@ -36,6 +36,13 @@ def count_instr_recursively(f, opname):
|
|||
return count
|
||||
|
||||
|
||||
def get_binop_argval(arg):
|
||||
for i, nb_op in enumerate(opcode._nb_ops):
|
||||
if arg == nb_op[0]:
|
||||
return i
|
||||
assert False, f"{arg} is not a valid BINARY_OP argument."
|
||||
|
||||
|
||||
class TestTranforms(BytecodeTestCase):
|
||||
|
||||
def check_jump_targets(self, code):
|
||||
|
@ -518,8 +525,7 @@ class TestTranforms(BytecodeTestCase):
|
|||
('("a" * 10)[10]', True),
|
||||
('(1, (1, 2))[2:6][0][2-1]', True),
|
||||
]
|
||||
subscr_argval = 26
|
||||
assert opcode._nb_ops[subscr_argval][0] == 'NB_SUBSCR'
|
||||
subscr_argval = get_binop_argval('NB_SUBSCR')
|
||||
for expr, has_error in tests:
|
||||
with self.subTest(expr=expr, has_error=has_error):
|
||||
code = compile(expr, '', 'single')
|
||||
|
@ -1062,6 +1068,200 @@ class DirectCfgOptimizerTests(CfgOptimizationTestCase):
|
|||
consts=[0, 1, 2, 3, 4],
|
||||
expected_consts=[0, 2, 3])
|
||||
|
||||
def test_list_exceeding_stack_use_guideline(self):
|
||||
def f():
|
||||
return [
|
||||
0, 1, 2, 3, 4,
|
||||
5, 6, 7, 8, 9,
|
||||
10, 11, 12, 13, 14,
|
||||
15, 16, 17, 18, 19,
|
||||
20, 21, 22, 23, 24,
|
||||
25, 26, 27, 28, 29,
|
||||
30, 31, 32, 33, 34,
|
||||
35, 36, 37, 38, 39
|
||||
]
|
||||
self.assertEqual(f(), list(range(40)))
|
||||
|
||||
def test_set_exceeding_stack_use_guideline(self):
|
||||
def f():
|
||||
return {
|
||||
0, 1, 2, 3, 4,
|
||||
5, 6, 7, 8, 9,
|
||||
10, 11, 12, 13, 14,
|
||||
15, 16, 17, 18, 19,
|
||||
20, 21, 22, 23, 24,
|
||||
25, 26, 27, 28, 29,
|
||||
30, 31, 32, 33, 34,
|
||||
35, 36, 37, 38, 39
|
||||
}
|
||||
self.assertEqual(f(), frozenset(range(40)))
|
||||
|
||||
def test_multiple_foldings(self):
|
||||
before = [
|
||||
('LOAD_SMALL_INT', 1, 0),
|
||||
('LOAD_SMALL_INT', 2, 0),
|
||||
('BUILD_TUPLE', 1, 0),
|
||||
('LOAD_SMALL_INT', 0, 0),
|
||||
('BINARY_OP', get_binop_argval('NB_SUBSCR'), 0),
|
||||
('BUILD_TUPLE', 2, 0),
|
||||
('RETURN_VALUE', None, 0)
|
||||
]
|
||||
after = [
|
||||
('LOAD_CONST', 1, 0),
|
||||
('RETURN_VALUE', None, 0)
|
||||
]
|
||||
self.cfg_optimization_test(before, after, consts=[], expected_consts=[(2,), (1, 2)])
|
||||
|
||||
def test_build_empty_tuple(self):
|
||||
before = [
|
||||
('BUILD_TUPLE', 0, 0),
|
||||
('RETURN_VALUE', None, 0),
|
||||
]
|
||||
after = [
|
||||
('LOAD_CONST', 0, 0),
|
||||
('RETURN_VALUE', None, 0),
|
||||
]
|
||||
self.cfg_optimization_test(before, after, consts=[], expected_consts=[()])
|
||||
|
||||
def test_fold_tuple_of_constants(self):
|
||||
before = [
|
||||
('NOP', None, 0),
|
||||
('LOAD_SMALL_INT', 1, 0),
|
||||
('NOP', None, 0),
|
||||
('LOAD_SMALL_INT', 2, 0),
|
||||
('NOP', None, 0),
|
||||
('NOP', None, 0),
|
||||
('LOAD_SMALL_INT', 3, 0),
|
||||
('NOP', None, 0),
|
||||
('BUILD_TUPLE', 3, 0),
|
||||
('RETURN_VALUE', None, 0),
|
||||
]
|
||||
after = [
|
||||
('LOAD_CONST', 0, 0),
|
||||
('RETURN_VALUE', None, 0),
|
||||
]
|
||||
self.cfg_optimization_test(before, after, consts=[], expected_consts=[(1, 2, 3)])
|
||||
|
||||
# not enough consts
|
||||
same = [
|
||||
('LOAD_SMALL_INT', 1, 0),
|
||||
('LOAD_SMALL_INT', 2, 0),
|
||||
('BUILD_TUPLE', 3, 0),
|
||||
('RETURN_VALUE', None, 0)
|
||||
]
|
||||
self.cfg_optimization_test(same, same, consts=[])
|
||||
|
||||
# not all consts
|
||||
same = [
|
||||
('LOAD_SMALL_INT', 1, 0),
|
||||
('LOAD_NAME', 0, 0),
|
||||
('LOAD_SMALL_INT', 2, 0),
|
||||
('BUILD_TUPLE', 3, 0),
|
||||
('RETURN_VALUE', None, 0)
|
||||
]
|
||||
self.cfg_optimization_test(same, same, consts=[])
|
||||
|
||||
def test_optimize_if_const_list(self):
|
||||
before = [
|
||||
('NOP', None, 0),
|
||||
('LOAD_SMALL_INT', 1, 0),
|
||||
('NOP', None, 0),
|
||||
('LOAD_SMALL_INT', 2, 0),
|
||||
('NOP', None, 0),
|
||||
('NOP', None, 0),
|
||||
('LOAD_SMALL_INT', 3, 0),
|
||||
('NOP', None, 0),
|
||||
('BUILD_LIST', 3, 0),
|
||||
('RETURN_VALUE', None, 0),
|
||||
]
|
||||
after = [
|
||||
('BUILD_LIST', 0, 0),
|
||||
('LOAD_CONST', 0, 0),
|
||||
('LIST_EXTEND', 1, 0),
|
||||
('RETURN_VALUE', None, 0),
|
||||
]
|
||||
self.cfg_optimization_test(before, after, consts=[], expected_consts=[(1, 2, 3)])
|
||||
|
||||
# need minimum 3 consts to optimize
|
||||
same = [
|
||||
('LOAD_SMALL_INT', 1, 0),
|
||||
('LOAD_SMALL_INT', 2, 0),
|
||||
('BUILD_LIST', 2, 0),
|
||||
('RETURN_VALUE', None, 0),
|
||||
]
|
||||
self.cfg_optimization_test(same, same, consts=[])
|
||||
|
||||
# not enough consts
|
||||
same = [
|
||||
('LOAD_SMALL_INT', 1, 0),
|
||||
('LOAD_SMALL_INT', 2, 0),
|
||||
('LOAD_SMALL_INT', 3, 0),
|
||||
('BUILD_LIST', 4, 0),
|
||||
('RETURN_VALUE', None, 0),
|
||||
]
|
||||
self.cfg_optimization_test(same, same, consts=[])
|
||||
|
||||
# not all consts
|
||||
same = [
|
||||
('LOAD_SMALL_INT', 1, 0),
|
||||
('LOAD_NAME', 0, 0),
|
||||
('LOAD_SMALL_INT', 3, 0),
|
||||
('BUILD_LIST', 3, 0),
|
||||
('RETURN_VALUE', None, 0),
|
||||
]
|
||||
self.cfg_optimization_test(same, same, consts=[])
|
||||
|
||||
def test_optimize_if_const_set(self):
|
||||
before = [
|
||||
('NOP', None, 0),
|
||||
('LOAD_SMALL_INT', 1, 0),
|
||||
('NOP', None, 0),
|
||||
('LOAD_SMALL_INT', 2, 0),
|
||||
('NOP', None, 0),
|
||||
('NOP', None, 0),
|
||||
('LOAD_SMALL_INT', 3, 0),
|
||||
('NOP', None, 0),
|
||||
('BUILD_SET', 3, 0),
|
||||
('RETURN_VALUE', None, 0),
|
||||
]
|
||||
after = [
|
||||
('BUILD_SET', 0, 0),
|
||||
('LOAD_CONST', 0, 0),
|
||||
('SET_UPDATE', 1, 0),
|
||||
('RETURN_VALUE', None, 0),
|
||||
]
|
||||
self.cfg_optimization_test(before, after, consts=[], expected_consts=[frozenset({1, 2, 3})])
|
||||
|
||||
# need minimum 3 consts to optimize
|
||||
same = [
|
||||
('LOAD_SMALL_INT', 1, 0),
|
||||
('LOAD_SMALL_INT', 2, 0),
|
||||
('BUILD_SET', 2, 0),
|
||||
('RETURN_VALUE', None, 0),
|
||||
]
|
||||
self.cfg_optimization_test(same, same, consts=[])
|
||||
|
||||
# not enough consts
|
||||
same = [
|
||||
('LOAD_SMALL_INT', 1, 0),
|
||||
('LOAD_SMALL_INT', 2, 0),
|
||||
('LOAD_SMALL_INT', 3, 0),
|
||||
('BUILD_SET', 4, 0),
|
||||
('RETURN_VALUE', None, 0),
|
||||
]
|
||||
self.cfg_optimization_test(same, same, consts=[])
|
||||
|
||||
# not all consts
|
||||
same = [
|
||||
('LOAD_SMALL_INT', 1, 0),
|
||||
('LOAD_NAME', 0, 0),
|
||||
('LOAD_SMALL_INT', 3, 0),
|
||||
('BUILD_SET', 3, 0),
|
||||
('RETURN_VALUE', None, 0),
|
||||
]
|
||||
self.cfg_optimization_test(same, same, consts=[])
|
||||
|
||||
|
||||
def test_conditional_jump_forward_const_condition(self):
|
||||
# The unreachable branch of the jump is removed, the jump
|
||||
# becomes redundant and is replaced by a NOP (for the lineno)
|
||||
|
|
|
@ -1338,15 +1338,64 @@ add_const(PyObject *newconst, PyObject *consts, PyObject *const_cache)
|
|||
return (int)index;
|
||||
}
|
||||
|
||||
static bool
|
||||
is_constant_sequence(cfg_instr *inst, int n)
|
||||
/*
|
||||
Walk basic block backwards starting from "start" trying to collect "size" number of
|
||||
subsequent constants from instructions loading constants into new tuple ignoring NOP's in between.
|
||||
|
||||
Returns ERROR on error and sets "seq" to NULL.
|
||||
Returns SUCCESS on success and sets "seq" to NULL if failed to collect requested number of constants.
|
||||
Returns SUCCESS on success and sets "seq" to resulting tuple if succeeded to collect requested number of constants.
|
||||
*/
|
||||
static int
|
||||
get_constant_sequence(basicblock *bb, int start, int size,
|
||||
PyObject *consts, PyObject **seq)
|
||||
{
|
||||
for (int i = 0; i < n; i++) {
|
||||
if(!loads_const(inst[i].i_opcode)) {
|
||||
return false;
|
||||
}
|
||||
assert(start < bb->b_iused);
|
||||
*seq = NULL;
|
||||
PyObject *res = PyTuple_New((Py_ssize_t)size);
|
||||
if (res == NULL) {
|
||||
return ERROR;
|
||||
}
|
||||
for (; start >= 0 && size > 0; start--) {
|
||||
cfg_instr *instr = &bb->b_instr[start];
|
||||
if (instr->i_opcode == NOP) {
|
||||
continue;
|
||||
}
|
||||
if (!loads_const(instr->i_opcode)) {
|
||||
break;
|
||||
}
|
||||
PyObject *constant = get_const_value(instr->i_opcode, instr->i_oparg, consts);
|
||||
if (constant == NULL) {
|
||||
Py_DECREF(res);
|
||||
return ERROR;
|
||||
}
|
||||
PyTuple_SET_ITEM(res, --size, constant);
|
||||
}
|
||||
if (size > 0) {
|
||||
Py_DECREF(res);
|
||||
}
|
||||
else {
|
||||
*seq = res;
|
||||
}
|
||||
return SUCCESS;
|
||||
}
|
||||
|
||||
/*
|
||||
Walk basic block backwards starting from "start" and change "count" number of
|
||||
non-NOP instructions to NOP's.
|
||||
*/
|
||||
static void
|
||||
nop_out(basicblock *bb, int start, int count)
|
||||
{
|
||||
assert(start < bb->b_iused);
|
||||
for (; count > 0; start--) {
|
||||
assert(start >= 0);
|
||||
if (bb->b_instr[start].i_opcode == NOP) {
|
||||
continue;
|
||||
}
|
||||
INSTR_SET_OP0(&bb->b_instr[start], NOP);
|
||||
count--;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/* Replace LOAD_CONST c1, LOAD_CONST c2 ... LOAD_CONST cn, BUILD_TUPLE n
|
||||
|
@ -1356,42 +1405,25 @@ is_constant_sequence(cfg_instr *inst, int n)
|
|||
Called with codestr pointing to the first LOAD_CONST.
|
||||
*/
|
||||
static int
|
||||
fold_tuple_on_constants(PyObject *const_cache,
|
||||
cfg_instr *inst,
|
||||
int n, PyObject *consts)
|
||||
fold_tuple_of_constants(basicblock *bb, int n, PyObject *consts, PyObject *const_cache)
|
||||
{
|
||||
/* Pre-conditions */
|
||||
assert(PyDict_CheckExact(const_cache));
|
||||
assert(PyList_CheckExact(consts));
|
||||
assert(inst[n].i_opcode == BUILD_TUPLE);
|
||||
assert(inst[n].i_oparg == n);
|
||||
|
||||
if (!is_constant_sequence(inst, n)) {
|
||||
cfg_instr *instr = &bb->b_instr[n];
|
||||
assert(instr->i_opcode == BUILD_TUPLE);
|
||||
int seq_size = instr->i_oparg;
|
||||
PyObject *newconst;
|
||||
RETURN_IF_ERROR(get_constant_sequence(bb, n-1, seq_size, consts, &newconst));
|
||||
if (newconst == NULL) {
|
||||
/* not a const sequence */
|
||||
return SUCCESS;
|
||||
}
|
||||
|
||||
/* Buildup new tuple of constants */
|
||||
PyObject *newconst = PyTuple_New(n);
|
||||
if (newconst == NULL) {
|
||||
return ERROR;
|
||||
}
|
||||
for (int i = 0; i < n; i++) {
|
||||
int op = inst[i].i_opcode;
|
||||
int arg = inst[i].i_oparg;
|
||||
PyObject *constant = get_const_value(op, arg, consts);
|
||||
if (constant == NULL) {
|
||||
return ERROR;
|
||||
}
|
||||
PyTuple_SET_ITEM(newconst, i, constant);
|
||||
}
|
||||
assert(PyTuple_CheckExact(newconst) && PyTuple_GET_SIZE(newconst) == seq_size);
|
||||
int index = add_const(newconst, consts, const_cache);
|
||||
if (index < 0) {
|
||||
return ERROR;
|
||||
}
|
||||
for (int i = 0; i < n; i++) {
|
||||
INSTR_SET_OP0(&inst[i], NOP);
|
||||
}
|
||||
INSTR_SET_OP1(&inst[n], LOAD_CONST, index);
|
||||
RETURN_IF_ERROR(index);
|
||||
nop_out(bb, n-1, seq_size);
|
||||
INSTR_SET_OP1(&bb->b_instr[n], LOAD_CONST, index);
|
||||
return SUCCESS;
|
||||
}
|
||||
|
||||
|
@ -1401,52 +1433,45 @@ fold_tuple_on_constants(PyObject *const_cache,
|
|||
or BUILD_SET & SET_UPDATE respectively.
|
||||
*/
|
||||
static int
|
||||
optimize_if_const_list_or_set(PyObject *const_cache, cfg_instr* inst, int n, PyObject *consts)
|
||||
optimize_if_const_list_or_set(basicblock *bb, int n, PyObject *consts, PyObject *const_cache)
|
||||
{
|
||||
assert(PyDict_CheckExact(const_cache));
|
||||
assert(PyList_CheckExact(consts));
|
||||
assert(inst[n].i_oparg == n);
|
||||
|
||||
int build = inst[n].i_opcode;
|
||||
assert(build == BUILD_LIST || build == BUILD_SET);
|
||||
int extend = build == BUILD_LIST ? LIST_EXTEND : SET_UPDATE;
|
||||
|
||||
if (n < MIN_CONST_SEQUENCE_SIZE || !is_constant_sequence(inst, n)) {
|
||||
cfg_instr *instr = &bb->b_instr[n];
|
||||
assert(instr->i_opcode == BUILD_LIST || instr->i_opcode == BUILD_SET);
|
||||
int seq_size = instr->i_oparg;
|
||||
if (seq_size < MIN_CONST_SEQUENCE_SIZE) {
|
||||
return SUCCESS;
|
||||
}
|
||||
PyObject *newconst = PyTuple_New(n);
|
||||
PyObject *newconst;
|
||||
RETURN_IF_ERROR(get_constant_sequence(bb, n-1, seq_size, consts, &newconst));
|
||||
if (newconst == NULL) {
|
||||
return ERROR;
|
||||
}
|
||||
for (int i = 0; i < n; i++) {
|
||||
int op = inst[i].i_opcode;
|
||||
int arg = inst[i].i_oparg;
|
||||
PyObject *constant = get_const_value(op, arg, consts);
|
||||
if (constant == NULL) {
|
||||
return ERROR;
|
||||
}
|
||||
PyTuple_SET_ITEM(newconst, i, constant);
|
||||
/* not a const sequence */
|
||||
return SUCCESS;
|
||||
}
|
||||
assert(PyTuple_CheckExact(newconst) && PyTuple_GET_SIZE(newconst) == seq_size);
|
||||
int build = instr->i_opcode;
|
||||
int extend = build == BUILD_LIST ? LIST_EXTEND : SET_UPDATE;
|
||||
if (build == BUILD_SET) {
|
||||
PyObject *frozenset = PyFrozenSet_New(newconst);
|
||||
if (frozenset == NULL) {
|
||||
Py_DECREF(newconst);
|
||||
return ERROR;
|
||||
}
|
||||
Py_SETREF(newconst, frozenset);
|
||||
}
|
||||
int index = add_const(newconst, consts, const_cache);
|
||||
RETURN_IF_ERROR(index);
|
||||
INSTR_SET_OP1(&inst[0], build, 0);
|
||||
for (int i = 1; i < n - 1; i++) {
|
||||
INSTR_SET_OP0(&inst[i], NOP);
|
||||
}
|
||||
INSTR_SET_OP1(&inst[n-1], LOAD_CONST, index);
|
||||
INSTR_SET_OP1(&inst[n], extend, 1);
|
||||
nop_out(bb, n-1, seq_size);
|
||||
assert(n >= 2);
|
||||
INSTR_SET_OP1(&bb->b_instr[n-2], build, 0);
|
||||
INSTR_SET_OP1(&bb->b_instr[n-1], LOAD_CONST, index);
|
||||
INSTR_SET_OP1(&bb->b_instr[n], extend, 1);
|
||||
return SUCCESS;
|
||||
}
|
||||
|
||||
/*
|
||||
Walk basic block upwards starting from "start" to collect instruction pair
|
||||
Walk basic block backwards starting from "start" to collect instruction pair
|
||||
that loads consts skipping NOP's in between.
|
||||
*/
|
||||
static bool
|
||||
|
@ -1894,19 +1919,11 @@ optimize_basic_block(PyObject *const_cache, basicblock *bb, PyObject *consts)
|
|||
continue;
|
||||
}
|
||||
}
|
||||
if (i >= oparg) {
|
||||
if (fold_tuple_on_constants(const_cache, inst-oparg, oparg, consts)) {
|
||||
goto error;
|
||||
}
|
||||
}
|
||||
RETURN_IF_ERROR(fold_tuple_of_constants(bb, i, consts, const_cache));
|
||||
break;
|
||||
case BUILD_LIST:
|
||||
case BUILD_SET:
|
||||
if (i >= oparg) {
|
||||
if (optimize_if_const_list_or_set(const_cache, inst-oparg, oparg, consts) < 0) {
|
||||
goto error;
|
||||
}
|
||||
}
|
||||
RETURN_IF_ERROR(optimize_if_const_list_or_set(bb, i, consts, const_cache));
|
||||
break;
|
||||
case POP_JUMP_IF_NOT_NONE:
|
||||
case POP_JUMP_IF_NONE:
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue