SF patch #1031667: Fold tuples of constants into a single constant

Example:
>>> import dis
>>> dis.dis(compile('1,2,3', '', 'eval'))
  0           0 LOAD_CONST               3 ((1, 2, 3))
              3 RETURN_VALUE
This commit is contained in:
Raymond Hettinger 2004-09-22 18:44:21 +00:00
parent 0318a939dd
commit 2c31a058eb
4 changed files with 109 additions and 15 deletions

View File

@ -64,15 +64,25 @@ class TestTranforms(unittest.TestCase):
def test_pack_unpack(self):
for line, elem in (
('a, = 1,', 'LOAD_CONST',),
('a, b = 1, 2', 'ROT_TWO',),
('a, b, c = 1, 2, 3', 'ROT_THREE',),
('a, = a,', 'LOAD_CONST',),
('a, b = a, b', 'ROT_TWO',),
('a, b, c = a, b, c', 'ROT_THREE',),
):
asm = dis_single(line)
self.assert_(elem in asm)
self.assert_('BUILD_TUPLE' not in asm)
self.assert_('UNPACK_TUPLE' not in asm)
def test_folding_of_tuples_of_constants(self):
for line, elem in (
('a = 1,2,3', '((1, 2, 3))',),
('("a","b","c")', "(('a', 'b', 'c'))",),
('a,b,c = 1,2,3', '((1, 2, 3))',),
):
asm = dis_single(line)
self.assert_(elem in asm)
self.assert_('BUILD_TUPLE' not in asm)
def test_elim_extra_return(self):
# RETURN LOAD_CONST None RETURN --> RETURN
def f(x):

View File

@ -12,6 +12,9 @@ What's New in Python 2.4 beta 1?
Core and builtins
-----------------
- The bytecode optimizer now folds tuples of constants into a single
constant.
- PyLong_AsUnsignedLong[Mask] now support int objects as well.
Extension modules

View File

@ -380,6 +380,8 @@ intern_strings(PyObject *tuple)
}
}
/* Begin: Peephole optimizations ----------------------------------------- */
#define GETARG(arr, i) ((int)((arr[i+2]<<8) + arr[i+1]))
#define UNCONDITIONAL_JUMP(op) (op==JUMP_ABSOLUTE || op==JUMP_FORWARD)
#define ABSOLUTE_JUMP(op) (op==JUMP_ABSOLUTE || op==CONTINUE_LOOP)
@ -388,6 +390,56 @@ intern_strings(PyObject *tuple)
#define CODESIZE(op) (HAS_ARG(op) ? 3 : 1)
#define ISBASICBLOCK(blocks, start, bytes) (blocks[start]==blocks[start+bytes-1])
/* Replace LOAD_CONST c1. LOAD_CONST c2 ... LOAD_CONST cn BUILD_TUPLE n
with LOAD_CONST (c1, c2, ... cn).
The consts table must still be in list form so that the
new constant (c1, c2, ... cn) can be appended.
Called with codestr pointing to the first LOAD_CONST.
Bails out with no change if one or more of the LOAD_CONSTs is missing. */
static int
tuple_of_constants(unsigned char *codestr, int n, PyObject *consts)
{
PyObject *newconst, *constant;
int i, arg, len_consts;
/* Pre-conditions */
assert(PyList_CheckExact(consts));
assert(codestr[0] == LOAD_CONST);
assert(codestr[n*3] == BUILD_TUPLE);
assert(GETARG(codestr, (n*3)) == n);
/* Verify chain of n load_constants */
for (i=0 ; i<n ; i++)
if (codestr[i*3] != LOAD_CONST)
return 0;
/* Buildup new tuple of constants */
newconst = PyTuple_New(n);
if (newconst == NULL)
return 0;
for (i=0 ; i<n ; i++) {
arg = GETARG(codestr, (i*3));
constant = PyList_GET_ITEM(consts, arg);
Py_INCREF(constant);
PyTuple_SET_ITEM(newconst, i, constant);
}
/* Append folded constant onto consts */
len_consts = PyList_GET_SIZE(consts);
if (PyList_Append(consts, newconst)) {
Py_DECREF(newconst);
return 0;
}
Py_DECREF(newconst);
/* Write NOPs over old LOAD_CONSTS and
add a new LOAD_CONST newconst on top of the BUILD_TUPLE n */
memset(codestr, NOP, n*3);
codestr[n*3] = LOAD_CONST;
SETARG(codestr, (n*3), len_consts);
return 1;
}
static unsigned int *
markblocks(unsigned char *code, int len)
{
@ -423,6 +475,21 @@ markblocks(unsigned char *code, int len)
return blocks;
}
/* Perform basic peephole optimizations to components of a code object.
The consts object should still be in list form to allow new constants
to be appended.
To keep the optimizer simple, it bails out (does nothing) for code
containing extended arguments or that has a length over 32,700. That
allows us to avoid overflow and sign issues. Likewise, it bails when
the lineno table has complex encoding for gaps >= 255.
Optimizations are restricted to simple transformations occuring within a
single basic block. All transformations keep the code size the same or
smaller. For those that reduce size, the gaps are initially filled with
NOPs. Later those NOPs are removed and the jump addresses retargeted in
a single pass. Line numbering is adjusted accordingly. */
static PyObject *
optimize_code(PyObject *code, PyObject* consts, PyObject *names, PyObject *lineno_obj)
{
@ -447,7 +514,7 @@ optimize_code(PyObject *code, PyObject* consts, PyObject *names, PyObject *linen
/* Avoid situations where jump retargeting could overflow */
codelen = PyString_Size(code);
if (codelen > 32000)
if (codelen > 32700)
goto exitUnchanged;
/* Make a modifiable copy of the code string */
@ -464,7 +531,7 @@ optimize_code(PyObject *code, PyObject* consts, PyObject *names, PyObject *linen
blocks = markblocks(codestr, codelen);
if (blocks == NULL)
goto exitUnchanged;
assert(PyTuple_Check(consts));
assert(PyList_Check(consts));
for (i=0, nops=0 ; i<codelen ; i += CODESIZE(codestr[i])) {
addrmap[i] = i - nops;
@ -511,8 +578,8 @@ optimize_code(PyObject *code, PyObject* consts, PyObject *names, PyObject *linen
name = PyString_AsString(PyTuple_GET_ITEM(names, j));
if (name == NULL || strcmp(name, "None") != 0)
continue;
for (j=0 ; j < PyTuple_GET_SIZE(consts) ; j++) {
if (PyTuple_GET_ITEM(consts, j) == Py_None) {
for (j=0 ; j < PyList_GET_SIZE(consts) ; j++) {
if (PyList_GET_ITEM(consts, j) == Py_None) {
codestr[i] = LOAD_CONST;
SETARG(codestr, i, j);
break;
@ -525,17 +592,28 @@ optimize_code(PyObject *code, PyObject* consts, PyObject *names, PyObject *linen
j = GETARG(codestr, i);
if (codestr[i+3] != JUMP_IF_FALSE ||
codestr[i+6] != POP_TOP ||
!ISBASICBLOCK(blocks,i,7) ||
!PyObject_IsTrue(PyTuple_GET_ITEM(consts, j)))
!ISBASICBLOCK(blocks,i,7) ||
!PyObject_IsTrue(PyList_GET_ITEM(consts, j)))
continue;
memset(codestr+i, NOP, 7);
nops += 7;
break;
/* Skip over BUILD_SEQN 1 UNPACK_SEQN 1.
/* Try to fold tuples of constants.
Skip over BUILD_SEQN 1 UNPACK_SEQN 1.
Replace BUILD_SEQN 2 UNPACK_SEQN 2 with ROT2.
Replace BUILD_SEQN 3 UNPACK_SEQN 3 with ROT3 ROT2. */
case BUILD_TUPLE:
j = GETARG(codestr, i);
h = i - 3 * j;
if (h >= 0 &&
codestr[h] == LOAD_CONST &&
ISBASICBLOCK(blocks, h, 3*(j+1)) &&
tuple_of_constants(&codestr[h], j, consts)) {
nops += 3 * j;
break;
}
/* Intentional fallthrough */
case BUILD_LIST:
j = GETARG(codestr, i);
if (codestr[i+3] != UNPACK_SEQUENCE ||
@ -610,8 +688,8 @@ optimize_code(PyObject *code, PyObject* consts, PyObject *names, PyObject *linen
/* Replace RETURN LOAD_CONST None RETURN with just RETURN */
case RETURN_VALUE:
if (i+4 >= codelen ||
codestr[i+4] != RETURN_VALUE ||
if (i+4 >= codelen ||
codestr[i+4] != RETURN_VALUE ||
!ISBASICBLOCK(blocks,i,5))
continue;
memset(codestr+i+1, NOP, 4);
@ -677,6 +755,8 @@ exitUnchanged:
return code;
}
/* End: Peephole optimizations ----------------------------------------- */
PyCodeObject *
PyCode_New(int argcount, int nlocals, int stacksize, int flags,
PyObject *code, PyObject *consts, PyObject *names,
@ -4899,7 +4979,6 @@ jcompile(node *n, const char *filename, struct compiling *base,
if (sc.c_errors == 0) {
PyObject *consts, *names, *varnames, *filename, *name,
*freevars, *cellvars, *code;
consts = PyList_AsTuple(sc.c_consts);
names = PyList_AsTuple(sc.c_names);
varnames = PyList_AsTuple(sc.c_varnames);
cellvars = dict_keys_inorder(sc.c_cellvars, 0);
@ -4907,7 +4986,8 @@ jcompile(node *n, const char *filename, struct compiling *base,
PyTuple_GET_SIZE(cellvars));
filename = PyString_InternFromString(sc.c_filename);
name = PyString_InternFromString(sc.c_name);
code = optimize_code(sc.c_code, consts, names, sc.c_lnotab);
code = optimize_code(sc.c_code, sc.c_consts, names, sc.c_lnotab);
consts = PyList_AsTuple(sc.c_consts);
if (!PyErr_Occurred())
co = PyCode_New(sc.c_argcount,
sc.c_nlocals,

View File

@ -49,8 +49,9 @@ extern time_t PyOS_GetLastModificationTime(char *, FILE *);
Python 2.3a0: 62011 (!)
Python 2.4a0: 62041
Python 2.4a3: 62051
Python 2.4b1: 62061
*/
#define MAGIC (62051 | ((long)'\r'<<16) | ((long)'\n'<<24))
#define MAGIC (62061 | ((long)'\r'<<16) | ((long)'\n'<<24))
/* Magic word as global; note that _PyImport_Init() can change the
value of this global to accommodate for alterations of how the