From c34f8673a194180ab3b258e821f72f25514aa948 Mon Sep 17 00:00:00 2001 From: Raymond Hettinger Date: Sun, 2 Jan 2005 06:17:33 +0000 Subject: [PATCH] Teach the peephole optimizer to fold simple constant expressions. --- Lib/test/test_peepholer.py | 28 +++++++++ Misc/NEWS | 3 + Python/compile.c | 119 ++++++++++++++++++++++++++++++++++++- 3 files changed, 149 insertions(+), 1 deletion(-) diff --git a/Lib/test/test_peepholer.py b/Lib/test/test_peepholer.py index f58fe03c7eb..a0031f21563 100644 --- a/Lib/test/test_peepholer.py +++ b/Lib/test/test_peepholer.py @@ -102,6 +102,34 @@ class TestTranforms(unittest.TestCase): 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, ],) + def test_folding_of_binops_on_constants(self): + for line, elem in ( + ('a = 2+3+4', '(9)'), # chained fold + ('"@"*4', "('@@@@')"), # check string ops + ('a="abc" + "def"', "('abcdef')"), # check string ops + ('a = 3**4', '(81)'), # binary power + ('a = 3*4', '(12)'), # binary multiply + ('a = 13/4.0', '(3.25)'), # binary divide + ('a = 13//4', '(3)'), # binary floor divide + ('a = 14%4', '(2)'), # binary modulo + ('a = 2+3', '(5)'), # binary add + ('a = 13-4', '(9)'), # binary subtract + ('a = (12,13)[1]', '(13)'), # binary subscr + ('a = 13 << 2', '(52)'), # binary lshift + ('a = 13 >> 2', '(3)'), # binary rshift + ('a = 13 & 7', '(5)'), # binary and + ('a = 13 ^ 7', '(10)'), # binary xor + ('a = 13 | 7', '(15)'), # binary or + ): + asm = dis_single(line) + self.assert_(elem in asm, asm) + self.assert_('BINARY_' not in asm) + + # Verify that unfoldables are skipped + asm = dis_single('a=2+"b"') + self.assert_('(2)' in asm) + self.assert_("('b')" in asm) + def test_elim_extra_return(self): # RETURN LOAD_CONST None RETURN --> RETURN def f(x): diff --git a/Misc/NEWS b/Misc/NEWS index af04ecf4e81..eb40d97b30c 100644 --- a/Misc/NEWS +++ b/Misc/NEWS @@ -13,6 +13,9 @@ Core and builtins - min() and max() now support key= arguments with the same meaning as in list.sort(). +- The peephole optimizer now performs simple constant folding in expressions: + (2+3) --> (5). + Extension Modules ----------------- diff --git a/Python/compile.c b/Python/compile.c index ea325ff0698..15c84361497 100644 --- a/Python/compile.c +++ b/Python/compile.c @@ -439,6 +439,99 @@ tuple_of_constants(unsigned char *codestr, int n, PyObject *consts) return 1; } +/* Replace LOAD_CONST c1. LOAD_CONST c2 BINOP + with LOAD_CONST binop(c1,c2) + The consts table must still be in list form so that the + new constant can be appended. + Called with codestr pointing to the first LOAD_CONST. + Abandons the transformation if the folding fails (i.e. 1+'a'). */ +static int +fold_binops_on_constants(unsigned char *codestr, PyObject *consts) +{ + PyObject *newconst, *v, *w; + int len_consts, opcode; + + /* Pre-conditions */ + assert(PyList_CheckExact(consts)); + assert(codestr[0] == LOAD_CONST); + assert(codestr[3] == LOAD_CONST); + + /* Create new constant */ + v = PyList_GET_ITEM(consts, GETARG(codestr, 0)); + w = PyList_GET_ITEM(consts, GETARG(codestr, 3)); + opcode = codestr[6]; + switch (opcode) { + case BINARY_POWER: + newconst = PyNumber_Power(v, w, Py_None); + break; + case BINARY_MULTIPLY: + newconst = PyNumber_Multiply(v, w); + break; + case BINARY_DIVIDE: + if (!_Py_QnewFlag) { + newconst = PyNumber_Divide(v, w); + break; + } + /* -Qnew is in effect: fall through to + BINARY_TRUE_DIVIDE */ + case BINARY_TRUE_DIVIDE: + newconst = PyNumber_TrueDivide(v, w); + break; + case BINARY_FLOOR_DIVIDE: + newconst = PyNumber_FloorDivide(v, w); + break; + case BINARY_MODULO: + newconst = PyNumber_Remainder(v, w); + break; + case BINARY_ADD: + newconst = PyNumber_Add(v, w); + break; + case BINARY_SUBTRACT: + newconst = PyNumber_Subtract(v, w); + break; + case BINARY_SUBSCR: + newconst = PyObject_GetItem(v, w); + break; + case BINARY_LSHIFT: + newconst = PyNumber_Lshift(v, w); + break; + case BINARY_RSHIFT: + newconst = PyNumber_Rshift(v, w); + break; + case BINARY_AND: + newconst = PyNumber_And(v, w); + break; + case BINARY_XOR: + newconst = PyNumber_Xor(v, w); + break; + case BINARY_OR: + newconst = PyNumber_Or(v, w); + break; + default: + /* Called with an unknown opcode */ + assert(0); + return 0; + } + if (newconst == NULL) { + PyErr_Clear(); + return 0; + } + + /* Append folded constant into consts table */ + len_consts = PyList_GET_SIZE(consts); + if (PyList_Append(consts, newconst)) { + Py_DECREF(newconst); + return 0; + } + Py_DECREF(newconst); + + /* Write NOP NOP NOP NOP LOAD_CONST newconst */ + memset(codestr, NOP, 4); + codestr[4] = LOAD_CONST; + SETARG(codestr, 4, len_consts); + return 1; +} + static unsigned int * markblocks(unsigned char *code, int len) { @@ -614,7 +707,6 @@ optimize_code(PyObject *code, PyObject* consts, PyObject *names, PyObject *linen h = i - 3 * j; if (h >= 0 && j <= lastlc && - codestr[h] == LOAD_CONST && ISBASICBLOCK(blocks, h, 3*(j+1)) && tuple_of_constants(&codestr[h], j, consts)) { assert(codestr[i] == LOAD_CONST); @@ -640,6 +732,31 @@ optimize_code(PyObject *code, PyObject* consts, PyObject *names, PyObject *linen } break; + /* Fold binary ops on constants. + LOAD_CONST c1 LOAD_CONST c2 BINOP --> LOAD_CONST binop(c1,c2) */ + case BINARY_POWER: + case BINARY_MULTIPLY: + case BINARY_DIVIDE: + case BINARY_TRUE_DIVIDE: + case BINARY_FLOOR_DIVIDE: + case BINARY_MODULO: + case BINARY_ADD: + case BINARY_SUBTRACT: + case BINARY_SUBSCR: + case BINARY_LSHIFT: + case BINARY_RSHIFT: + case BINARY_AND: + case BINARY_XOR: + case BINARY_OR: + if (lastlc >= 2 && + ISBASICBLOCK(blocks, i-6, 7) && + fold_binops_on_constants(&codestr[i-6], consts)) { + i -= 2; + assert(codestr[i] == LOAD_CONST); + cumlc = 1; + } + break; + /* Simplify conditional jump to conditional jump where the result of the first test implies the success of a similar test or the failure of the opposite test.