mirror of
https://github.com/python/cpython.git
synced 2025-10-17 20:28:43 +00:00
gh-126835: Move constant subscript folding to CFG (#129568)
Move folding of constant subscription from AST optimizer to CFG. Co-authored-by: Irit Katriel <1055913+iritkatriel@users.noreply.github.com>
This commit is contained in:
parent
bb5c6875d6
commit
0664c1af9b
6 changed files with 138 additions and 31 deletions
|
@ -65,6 +65,8 @@ PyAPI_FUNC(void) _PyLong_ExactDealloc(PyObject *self);
|
||||||
# error "_PY_NSMALLPOSINTS must be greater than or equal to 257"
|
# error "_PY_NSMALLPOSINTS must be greater than or equal to 257"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#define _PY_IS_SMALL_INT(val) ((val) >= 0 && (val) < 256 && (val) < _PY_NSMALLPOSINTS)
|
||||||
|
|
||||||
// Return a reference to the immortal zero singleton.
|
// Return a reference to the immortal zero singleton.
|
||||||
// The function cannot return NULL.
|
// The function cannot return NULL.
|
||||||
static inline PyObject* _PyLong_GetZero(void)
|
static inline PyObject* _PyLong_GetZero(void)
|
||||||
|
|
|
@ -3279,16 +3279,6 @@ class ASTOptimiziationTests(unittest.TestCase):
|
||||||
|
|
||||||
self.assert_ast(code % (left, right), non_optimized_target, optimized_target)
|
self.assert_ast(code % (left, right), non_optimized_target, optimized_target)
|
||||||
|
|
||||||
def test_folding_subscript(self):
|
|
||||||
code = "(1,)[0]"
|
|
||||||
|
|
||||||
non_optimized_target = self.wrap_expr(
|
|
||||||
ast.Subscript(value=ast.Tuple(elts=[ast.Constant(value=1)]), slice=ast.Constant(value=0))
|
|
||||||
)
|
|
||||||
optimized_target = self.wrap_expr(ast.Constant(value=1))
|
|
||||||
|
|
||||||
self.assert_ast(code, non_optimized_target, optimized_target)
|
|
||||||
|
|
||||||
def test_folding_type_param_in_function_def(self):
|
def test_folding_type_param_in_function_def(self):
|
||||||
code = "def foo[%s = 1 + 1](): pass"
|
code = "def foo[%s = 1 + 1](): pass"
|
||||||
|
|
||||||
|
|
|
@ -473,6 +473,59 @@ class TestTranforms(BytecodeTestCase):
|
||||||
self.assertFalse(instr.opname.startswith('BUILD_'))
|
self.assertFalse(instr.opname.startswith('BUILD_'))
|
||||||
self.check_lnotab(code)
|
self.check_lnotab(code)
|
||||||
|
|
||||||
|
def test_constant_folding_small_int(self):
|
||||||
|
tests = [
|
||||||
|
# subscript
|
||||||
|
('(0, )[0]', 0),
|
||||||
|
('(1 + 2, )[0]', 3),
|
||||||
|
('(2 + 2 * 2, )[0]', 6),
|
||||||
|
('(1, (1 + 2 + 3, ))[1][0]', 6),
|
||||||
|
('(255, )[0]', 255),
|
||||||
|
('(256, )[0]', None),
|
||||||
|
('(1000, )[0]', None),
|
||||||
|
('(1 - 2, )[0]', None),
|
||||||
|
]
|
||||||
|
for expr, oparg in tests:
|
||||||
|
with self.subTest(expr=expr, oparg=oparg):
|
||||||
|
code = compile(expr, '', 'single')
|
||||||
|
if oparg is not None:
|
||||||
|
self.assertInBytecode(code, 'LOAD_SMALL_INT', oparg)
|
||||||
|
else:
|
||||||
|
self.assertNotInBytecode(code, 'LOAD_SMALL_INT')
|
||||||
|
self.check_lnotab(code)
|
||||||
|
|
||||||
|
def test_folding_subscript(self):
|
||||||
|
tests = [
|
||||||
|
('(1, )[0]', False),
|
||||||
|
('(1, )[-1]', False),
|
||||||
|
('(1 + 2, )[0]', False),
|
||||||
|
('(1, (1, 2))[1][1]', False),
|
||||||
|
('(1, 2)[2-1]', False),
|
||||||
|
('(1, (1, 2))[1][2-1]', False),
|
||||||
|
('(1, (1, 2))[1:6][0][2-1]', False),
|
||||||
|
('"a"[0]', False),
|
||||||
|
('("a" + "b")[1]', False),
|
||||||
|
('("a" + "b", )[0][1]', False),
|
||||||
|
('("a" * 10)[9]', False),
|
||||||
|
('(1, )[1]', True),
|
||||||
|
('(1, )[-2]', True),
|
||||||
|
('"a"[1]', True),
|
||||||
|
('"a"[-2]', True),
|
||||||
|
('("a" + "b")[2]', True),
|
||||||
|
('("a" + "b", )[0][2]', True),
|
||||||
|
('("a" + "b", )[1][0]', True),
|
||||||
|
('("a" * 10)[10]', True),
|
||||||
|
('(1, (1, 2))[2:6][0][2-1]', True),
|
||||||
|
]
|
||||||
|
for expr, has_error in tests:
|
||||||
|
with self.subTest(expr=expr, has_error=has_error):
|
||||||
|
code = compile(expr, '', 'single')
|
||||||
|
if not has_error:
|
||||||
|
self.assertNotInBytecode(code, 'BINARY_SUBSCR')
|
||||||
|
else:
|
||||||
|
self.assertInBytecode(code, 'BINARY_SUBSCR')
|
||||||
|
self.check_lnotab(code)
|
||||||
|
|
||||||
def test_in_literal_list(self):
|
def test_in_literal_list(self):
|
||||||
def containtest():
|
def containtest():
|
||||||
return x in [a, b]
|
return x in [a, b]
|
||||||
|
|
|
@ -567,25 +567,6 @@ fold_tuple(expr_ty node, PyArena *arena, _PyASTOptimizeState *state)
|
||||||
return make_const(node, newval, arena);
|
return make_const(node, newval, arena);
|
||||||
}
|
}
|
||||||
|
|
||||||
static int
|
|
||||||
fold_subscr(expr_ty node, PyArena *arena, _PyASTOptimizeState *state)
|
|
||||||
{
|
|
||||||
PyObject *newval;
|
|
||||||
expr_ty arg, idx;
|
|
||||||
|
|
||||||
arg = node->v.Subscript.value;
|
|
||||||
idx = node->v.Subscript.slice;
|
|
||||||
if (node->v.Subscript.ctx != Load ||
|
|
||||||
arg->kind != Constant_kind ||
|
|
||||||
idx->kind != Constant_kind)
|
|
||||||
{
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
newval = PyObject_GetItem(arg->v.Constant.value, idx->v.Constant.value);
|
|
||||||
return make_const(node, newval, arena);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Change literal list or set of constants into constant
|
/* Change literal list or set of constants into constant
|
||||||
tuple or frozenset respectively. Change literal list of
|
tuple or frozenset respectively. Change literal list of
|
||||||
non-constants into tuple.
|
non-constants into tuple.
|
||||||
|
@ -822,7 +803,6 @@ astfold_expr(expr_ty node_, PyArena *ctx_, _PyASTOptimizeState *state)
|
||||||
case Subscript_kind:
|
case Subscript_kind:
|
||||||
CALL(astfold_expr, expr_ty, node_->v.Subscript.value);
|
CALL(astfold_expr, expr_ty, node_->v.Subscript.value);
|
||||||
CALL(astfold_expr, expr_ty, node_->v.Subscript.slice);
|
CALL(astfold_expr, expr_ty, node_->v.Subscript.slice);
|
||||||
CALL(fold_subscr, expr_ty, node_);
|
|
||||||
break;
|
break;
|
||||||
case Starred_kind:
|
case Starred_kind:
|
||||||
CALL(astfold_expr, expr_ty, node_->v.Starred.value);
|
CALL(astfold_expr, expr_ty, node_->v.Starred.value);
|
||||||
|
|
|
@ -284,7 +284,7 @@ codegen_addop_load_const(compiler *c, location loc, PyObject *o)
|
||||||
if (PyLong_CheckExact(o)) {
|
if (PyLong_CheckExact(o)) {
|
||||||
int overflow;
|
int overflow;
|
||||||
long val = PyLong_AsLongAndOverflow(o, &overflow);
|
long val = PyLong_AsLongAndOverflow(o, &overflow);
|
||||||
if (!overflow && val >= 0 && val < 256 && val < _PY_NSMALLPOSINTS) {
|
if (!overflow && _PY_IS_SMALL_INT(val)) {
|
||||||
ADDOP_I(c, loc, LOAD_SMALL_INT, val);
|
ADDOP_I(c, loc, LOAD_SMALL_INT, val);
|
||||||
return SUCCESS;
|
return SUCCESS;
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,6 +6,7 @@
|
||||||
#include "pycore_compile.h"
|
#include "pycore_compile.h"
|
||||||
#include "pycore_intrinsics.h"
|
#include "pycore_intrinsics.h"
|
||||||
#include "pycore_pymem.h" // _PyMem_IsPtrFreed()
|
#include "pycore_pymem.h" // _PyMem_IsPtrFreed()
|
||||||
|
#include "pycore_long.h" // _PY_IS_SMALL_INT()
|
||||||
|
|
||||||
#include "pycore_opcode_utils.h"
|
#include "pycore_opcode_utils.h"
|
||||||
#include "pycore_opcode_metadata.h" // OPCODE_HAS_ARG, etc
|
#include "pycore_opcode_metadata.h" // OPCODE_HAS_ARG, etc
|
||||||
|
@ -1443,6 +1444,84 @@ optimize_if_const_list_or_set(PyObject *const_cache, cfg_instr* inst, int n, PyO
|
||||||
return SUCCESS;
|
return SUCCESS;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Walk basic block upwards starting from "start" to collect instruction pair
|
||||||
|
that loads consts skipping NOP's in between.
|
||||||
|
*/
|
||||||
|
static bool
|
||||||
|
find_load_const_pair(basicblock *bb, int start, cfg_instr **first, cfg_instr **second)
|
||||||
|
{
|
||||||
|
cfg_instr *second_load_const = NULL;
|
||||||
|
while (start >= 0) {
|
||||||
|
cfg_instr *inst = &bb->b_instr[start--];
|
||||||
|
if (inst->i_opcode == NOP) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (!loads_const(inst->i_opcode)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (second_load_const == NULL) {
|
||||||
|
second_load_const = inst;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
*first = inst;
|
||||||
|
*second = second_load_const;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Determine opcode & oparg for freshly folded constant. */
|
||||||
|
static int
|
||||||
|
newop_from_folded(PyObject *newconst, PyObject *consts,
|
||||||
|
PyObject *const_cache, int *newopcode, int *newoparg)
|
||||||
|
{
|
||||||
|
if (PyLong_CheckExact(newconst)) {
|
||||||
|
int overflow;
|
||||||
|
long val = PyLong_AsLongAndOverflow(newconst, &overflow);
|
||||||
|
if (!overflow && _PY_IS_SMALL_INT(val)) {
|
||||||
|
*newopcode = LOAD_SMALL_INT;
|
||||||
|
*newoparg = val;
|
||||||
|
return SUCCESS;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*newopcode = LOAD_CONST;
|
||||||
|
*newoparg = add_const(newconst, consts, const_cache);
|
||||||
|
RETURN_IF_ERROR(*newoparg);
|
||||||
|
return SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
optimize_if_const_subscr(basicblock *bb, int n, PyObject *consts, PyObject *const_cache)
|
||||||
|
{
|
||||||
|
cfg_instr *subscr = &bb->b_instr[n];
|
||||||
|
assert(subscr->i_opcode == BINARY_SUBSCR);
|
||||||
|
cfg_instr *arg, *idx;
|
||||||
|
if (!find_load_const_pair(bb, n-1, &arg, &idx)) {
|
||||||
|
return SUCCESS;
|
||||||
|
}
|
||||||
|
PyObject *o, *key;
|
||||||
|
if ((o = get_const_value(arg->i_opcode, arg->i_oparg, consts)) == NULL
|
||||||
|
|| (key = get_const_value(idx->i_opcode, idx->i_oparg, consts)) == NULL)
|
||||||
|
{
|
||||||
|
return ERROR;
|
||||||
|
}
|
||||||
|
PyObject *newconst = PyObject_GetItem(o, key);
|
||||||
|
if (newconst == NULL) {
|
||||||
|
if (PyErr_ExceptionMatches(PyExc_KeyboardInterrupt)) {
|
||||||
|
return ERROR;
|
||||||
|
}
|
||||||
|
PyErr_Clear();
|
||||||
|
return SUCCESS;
|
||||||
|
}
|
||||||
|
int newopcode, newoparg;
|
||||||
|
RETURN_IF_ERROR(newop_from_folded(newconst, consts, const_cache, &newopcode, &newoparg));
|
||||||
|
INSTR_SET_OP1(subscr, newopcode, newoparg);
|
||||||
|
INSTR_SET_OP0(arg, NOP);
|
||||||
|
INSTR_SET_OP0(idx, NOP);
|
||||||
|
return SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
#define VISITED (-1)
|
#define VISITED (-1)
|
||||||
|
|
||||||
// Replace an arbitrary run of SWAPs and NOPs with an optimal one that has the
|
// Replace an arbitrary run of SWAPs and NOPs with an optimal one that has the
|
||||||
|
@ -1948,6 +2027,9 @@ optimize_basic_block(PyObject *const_cache, basicblock *bb, PyObject *consts)
|
||||||
INSTR_SET_OP0(inst, NOP);
|
INSTR_SET_OP0(inst, NOP);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
case BINARY_SUBSCR:
|
||||||
|
RETURN_IF_ERROR(optimize_if_const_subscr(bb, i, consts, const_cache));
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue