[pypy-svn] r66549 - in pypy/branch/parser-compiler/pypy/interpreter: . astcompiler test

benjamin at codespeak.net benjamin at codespeak.net
Thu Jul 23 18:54:35 CEST 2009


Author: benjamin
Date: Thu Jul 23 18:54:34 2009
New Revision: 66549

Modified:
   pypy/branch/parser-compiler/pypy/interpreter/astcompiler/optimize.py
   pypy/branch/parser-compiler/pypy/interpreter/pycompiler.py
   pypy/branch/parser-compiler/pypy/interpreter/test/test_compiler.py
Log:
add a AST visitor which folds constants

Modified: pypy/branch/parser-compiler/pypy/interpreter/astcompiler/optimize.py
==============================================================================
--- pypy/branch/parser-compiler/pypy/interpreter/astcompiler/optimize.py	(original)
+++ pypy/branch/parser-compiler/pypy/interpreter/astcompiler/optimize.py	Thu Jul 23 18:54:34 2009
@@ -1,5 +1,14 @@
-from pypy.interpreter.astcompiler import ast2 as ast
+import sys
+import itertools
+
+from pypy.interpreter.astcompiler import ast2 as ast, consts, misc
 from pypy.tool import stdlib_opcode as ops
+from pypy.interpreter.error import OperationError
+from pypy.rlib.unroll import unrolling_iterable
+
+
+def optimize_ast(space, tree, compile_info):
+    return tree.mutate_over(OptimizingVisitor(space, compile_info))
 
 
 CONST_NOT_CONST = -1
@@ -38,6 +47,12 @@
         return self.s
 
 
+class __extend__(ast.Const):
+
+    def as_constant(self):
+        return self.value
+
+
 class __extend__(ast.UnaryOp):
 
     def accept_jump_if(self, gen, condition, target):
@@ -65,3 +80,164 @@
             gen.use_next_block(end)
         else:
             self._accept_jump_if_any_is(gen, condition, target)
+
+
+def _binary_fold(name):
+    def do_fold(space, left, right):
+        return getattr(space, name)(left, right)
+    return do_fold
+
+def _unary_fold(name):
+    def do_fold(space, operand):
+        return getattr(space, name)(operand)
+    return do_fold
+
+def _fold_pow(space, left, right):
+    return space.pow(left, right, space.w_None)
+
+def _fold_not(space, operand):
+    return space.wrap(not space.is_true(operand))
+
+
+binary_folders = {
+    ast.Add : _binary_fold("add"),
+    ast.Sub : _binary_fold("sub"),
+    ast.Mult : _binary_fold("mul"),
+    ast.Div : _binary_fold("truediv"),
+    ast.FloorDiv : _binary_fold("floordiv"),
+    ast.Mod : _binary_fold("mod"),
+    ast.Pow : _fold_pow,
+    ast.LShift : _binary_fold("lshift"),
+    ast.RShift : _binary_fold("rshift"),
+    ast.BitOr : _binary_fold("or_"),
+    ast.BitXor : _binary_fold("xor"),
+    ast.BitAnd : _binary_fold("and_")
+}
+unrolling_binary_folders = unrolling_iterable(binary_folders.items())
+
+unary_folders = {
+    ast.Not : _fold_not,
+    ast.USub : _unary_fold("neg"),
+    ast.UAdd : _unary_fold("pos"),
+    ast.Invert : _unary_fold("invert")
+}
+unrolling_unary_folders = unrolling_iterable(unary_folders.items())
+
+for folder in itertools.chain(binary_folders.itervalues(),
+                              unary_folders.itervalues()):
+    folder._always_inline_ = True
+del folder
+
+opposite_compare_operations = misc.dict_to_switch({
+    ast.Is : ast.IsNot,
+    ast.IsNot : ast.Is,
+    ast.In : ast.NotIn,
+    ast.NotIn : ast.In
+})
+
+
+class OptimizingVisitor(ast.ASTVisitor):
+
+    def __init__(self, space, compile_info):
+        self.space = space
+        self.compile_info = compile_info
+
+    def default_visitor(self, node):
+        return node
+
+    def visit_BinOp(self, binop):
+        left = binop.left.as_constant()
+        if left is not None:
+            right = binop.right.as_constant()
+            if right is not None:
+                op = binop.op
+                if op == ast.Div and \
+                        not self.compile_info.flags & consts.CO_FUTURE_DIVISION:
+                    return binop
+                try:
+                    for op_kind, folder in unrolling_binary_folders:
+                        if op_kind == op:
+                            w_const = folder(self.space, left, right)
+                except OperationError:
+                    pass
+                else:
+                    try:
+                        w_len = self.space.len(w_const)
+                    except OperationError:
+                        pass
+                    else:
+                        if self.space.int_w(w_len) > 20:
+                            return binop
+                    return ast.Const(w_const, binop.lineno, binop.col_offset)
+        return binop
+
+    def visit_UnaryOp(self, unary):
+        w_operand = unary.operand.as_constant()
+        op = unary.op
+        if w_operand is not None:
+            try:
+                for op_kind, folder in unrolling_unary_folders:
+                    if op_kind == op:
+                        w_const = folder(self.space, w_operand)
+                w_minint = self.space.wrap(-sys.maxint - 1)
+                # This makes sure the result is an integer.
+                if self.space.eq_w(w_minint, w_const):
+                    w_const = w_minint
+            except OperationError:
+                pass
+            else:
+                return ast.Const(w_const, unary.lineno, unary.col_offset)
+        elif op == ast.Not:
+            compare = unary.operand
+            if isinstance(compare, ast.Compare) and len(compare.ops) == 1:
+                cmp_op = compare.ops[0]
+                try:
+                    opposite = opposite_compare_operations(cmp_op)
+                except KeyError:
+                    pass
+                else:
+                    compare.ops[0] = opposite
+                    return compare
+        return unary
+
+    def visit_BoolOp(self, bop):
+        values = bop.values
+        we_are_and = bop.op == ast.And
+        i = 0
+        while i < len(values) - 1:
+            truth = values[i].as_constant_truth(self.space)
+            if truth != CONST_NOT_CONST:
+                if truth == CONST_FALSE == we_are_and:
+                    del values[i + 1:]
+                    break
+                else:
+                    del values[i]
+            else:
+                i += 1
+        if len(values) == 1:
+            return values[0]
+        return bop
+
+    def visit_Repr(self, rep):
+        w_const = rep.value.as_constant()
+        if w_const is not None:
+            w_repr = self.space.repr(w_const)
+            return ast.Const(w_repr, rep.lineno, rep.col_offset)
+        return rep
+
+    def visit_Name(self, name):
+        if name.id == "None":
+            assert name.ctx == ast.Load
+            return ast.Const(self.space.w_None, name.lineno, name.col_offset)
+        return name
+
+    def visit_Tuple(self, tup):
+        consts_w = []
+        if tup.elts:
+            for node in tup.elts:
+                w_const = node.as_constant()
+                if w_const is None:
+                    return tup
+                consts_w.append(w_const)
+        w_consts = self.space.newtuple(consts_w)
+        return ast.Const(w_consts, tup.lineno, tup.col_offset)

Modified: pypy/branch/parser-compiler/pypy/interpreter/pycompiler.py
==============================================================================
--- pypy/branch/parser-compiler/pypy/interpreter/pycompiler.py	(original)
+++ pypy/branch/parser-compiler/pypy/interpreter/pycompiler.py	Thu Jul 23 18:54:34 2009
@@ -231,7 +231,7 @@
         from pypy.interpreter.pyparser.pythonlexer import TokenIndentationError
         from pypy.interpreter.astcompiler.astbuilder import ast_from_node
         from pypy.interpreter.astcompiler.codegen import compile_ast
-        from pypy.interpreter.astcompiler import consts
+        from pypy.interpreter.astcompiler import consts, optimize
 
         space = self.space
 
@@ -247,6 +247,7 @@
             info = CompileInfo(filename, mode, flags, future_lineno)
             parse_tree = self.parser.parse_source(source, info)
             module = ast_from_node(space, parse_tree, info)
+            module = optimize.optimize_ast(space, module, info)
             code = compile_ast(space, module, info)
         except IndentationError, e:
             raise OperationError(space.w_IndentationError,

Modified: pypy/branch/parser-compiler/pypy/interpreter/test/test_compiler.py
==============================================================================
--- pypy/branch/parser-compiler/pypy/interpreter/test/test_compiler.py	(original)
+++ pypy/branch/parser-compiler/pypy/interpreter/test/test_compiler.py	Thu Jul 23 18:54:34 2009
@@ -723,42 +723,6 @@
 ##        py.test.skip("unsupported")
 
 class AppTestOptimizer:
-    def test_constant_fold_add(self):
-        import parser
-        class Folder(object):
-            def defaultvisit(self, node):
-                return node
-
-            def __getattr__(self, attrname):
-                if attrname.startswith('visit'):
-                    return self.defaultvisit
-                raise AttributeError(attrname)
-
-            def visitAdd(self, node):
-                left = node.left
-                right = node.right
-                if isinstance(left, parser.ASTConst) and \
-                       isinstance(right, parser.ASTConst):
-                    if type(left.value) == type(right.value):
-                        return parser.ASTConst(left.value + right.value)
-                return node
-
-        def hook(ast, enc, filename):
-            return ast.mutate(Folder())
-
-        parser.install_compiler_hook(hook)
-        code = compile("1+2", "", "eval")
-        parser.install_compiler_hook(None)
-        import dis, sys, StringIO
-        s = StringIO.StringIO()
-        so = sys.stdout
-        sys.stdout = s
-        try:
-            dis.dis(code)
-        finally:
-            sys.stdout = so
-        output = s.getvalue()
-        assert 'BINARY_ADD' not in output
 
     def test_remove_ending(self):
         source = """def f():
@@ -777,7 +741,41 @@
         output = s.getvalue()
         assert output.count('LOAD_CONST') == 1
 
-    
+    def test_none_constant(self):
+        import opcode
+        co = compile("def f(): return None", "<test>", "exec").co_consts[0]
+        co = co.co_code
+        op = ord(co[0]) + (ord(co[1]) << 8)
+        assert op == opcode.opmap["LOAD_CONST"]
+
+    def test_division_folding(self):
+        def code(source):
+            return compile(source, "<test>", "exec")
+        co = code("x = 10//4")
+        assert len(co.co_consts) == 2
+        assert co.co_consts[0] == 2
+        co = code("x = 10/4")
+        assert len(co.co_consts) == 3
+        assert co.co_consts[:2] == (10, 4)
+        co = code("from __future__ import division\nx = 10/4")
+        assert co.co_consts[2] == 2.5
+
+    def test_tuple_folding(self):
+        co = compile("(1, 2, 3)", "<test>", "exec")
+        assert co.co_consts == ((1, 2, 3), None)
+        co = compile("()", "<test>", "exec")
+        assert co.co_consts == ((), None)
+
+    def test_unary_folding(self):
+        co = compile("-(3)", "<test>", "exec")
+        assert co.co_consts[0] == -3
+        co = compile("~3", "<test>", "exec")
+        assert co.co_consts[0] == ~3
+        co = compile("+(-3)", "<test>", "exec")
+        assert co.co_consts[0] == -3
+        co = compile("not None", "<test>", "exec")
+        assert co.co_consts[0] is True
+
     def test_folding_of_binops_on_constants(self):
         def disassemble(func):
             from StringIO import StringIO



More information about the Pypy-commit mailing list