[pypy-svn] r57935 - in pypy/dist/pypy: interpreter interpreter/test rpython/memory/gctransform rpython/memory/gctransform/test translator/test

pedronis at codespeak.net pedronis at codespeak.net
Sun Sep 7 12:31:57 CEST 2008


Author: pedronis
Date: Sun Sep  7 12:31:55 2008
New Revision: 57935

Modified:
   pypy/dist/pypy/interpreter/eval.py
   pypy/dist/pypy/interpreter/function.py
   pypy/dist/pypy/interpreter/pycode.py
   pypy/dist/pypy/interpreter/test/test_function.py
   pypy/dist/pypy/rpython/memory/gctransform/framework.py
   pypy/dist/pypy/rpython/memory/gctransform/test/test_framework.py
   pypy/dist/pypy/translator/test/test_stackcheck.py
Log:
merging garden-call-code-2

- don't use fastcall* shortcuts for PyCode, simply have uniform code for the flat f(a,b,c,d) case. Speeds up calls to python functions with more than 4 arguments

- avoid unneeded root reloading around stack_check in non-stackless builds: speeds up quite a bit of things except for the odd one
  because of our usual translation performance instabilities



Modified: pypy/dist/pypy/interpreter/eval.py
==============================================================================
--- pypy/dist/pypy/interpreter/eval.py	(original)
+++ pypy/dist/pypy/interpreter/eval.py	Sun Sep  7 12:31:55 2008
@@ -62,18 +62,6 @@
 
     def funcrun_obj(self, func, w_obj, args):
         return self.funcrun(func, args.prepend(w_obj))
-        
-    # a performance hack (see gateway.BuiltinCode1/2/3 and pycode.PyCode)
-    def fastcall_0(self, space, func):
-        raise NotImplementedError
-    def fastcall_1(self, space, func, w1):
-        raise NotImplementedError        
-    def fastcall_2(self, space, func, w1, w2):
-        raise NotImplementedError                
-    def fastcall_3(self, space, func, w1, w2, w3):
-        raise NotImplementedError
-    def fastcall_4(self, space, func, w1, w2, w3, w4):
-        raise NotImplementedError                                
 
 class Frame(Wrappable):
     """A frame is an environment supporting the execution of a code object.

Modified: pypy/dist/pypy/interpreter/function.py
==============================================================================
--- pypy/dist/pypy/interpreter/function.py	(original)
+++ pypy/dist/pypy/interpreter/function.py	Sun Sep  7 12:31:55 2008
@@ -6,11 +6,14 @@
 attribute.
 """
 
+from pypy.rlib.unroll import unrolling_iterable
 from pypy.interpreter.error import OperationError
 from pypy.interpreter.baseobjspace import Wrappable
 from pypy.interpreter.eval import Code
 from pypy.interpreter.argument import Arguments, ArgumentsFromValuestack
 
+funccallunrolling = unrolling_iterable(range(4))
+
 class Function(Wrappable):
     """A function is a code object captured with some environment:
     an object space, a dictionary of globals, default arguments,
@@ -44,24 +47,40 @@
         return self.code
     
     def funccall(self, *args_w): # speed hack
+        from pypy.interpreter import gateway
+        from pypy.interpreter.pycode import PyCode
+        
         code = self.getcode() # hook for the jit
         nargs = len(args_w)
         fast_natural_arity = code.fast_natural_arity
         if nargs == fast_natural_arity:
             if nargs == 0:
+                assert isinstance(code, gateway.BuiltinCode0)                
                 return code.fastcall_0(self.space, self)
             elif nargs == 1:
+                assert isinstance(code, gateway.BuiltinCode1)
                 return code.fastcall_1(self.space, self, args_w[0])
             elif nargs == 2:
+                assert isinstance(code, gateway.BuiltinCode2)
                 return code.fastcall_2(self.space, self, args_w[0], args_w[1])
             elif nargs == 3:
+                assert isinstance(code, gateway.BuiltinCode3)                
                 return code.fastcall_3(self.space, self, args_w[0],
                                        args_w[1], args_w[2])
             elif nargs == 4:
+                assert isinstance(code, gateway.BuiltinCode4)                
                 return code.fastcall_4(self.space, self, args_w[0],
                                        args_w[1], args_w[2], args_w[3])
+        elif (nargs|PyCode.FLATPYCALL) == fast_natural_arity:
+            assert isinstance(code, PyCode)            
+            if nargs < 5:
+                new_frame = self.space.createframe(code, self.w_func_globals,
+                                                   self.closure)
+                for i in funccallunrolling:
+                    if i < nargs:
+                        new_frame.fastlocals_w[i] = args_w[i]
+                return new_frame.run()                                    
         elif nargs >= 1 and fast_natural_arity == -1:
-            from pypy.interpreter import gateway
             assert isinstance(code, gateway.BuiltinCodePassThroughArguments1)
             return code.funcrun_obj(self, args_w[0],
                                     Arguments(self.space,
@@ -69,25 +88,35 @@
         return self.call_args(Arguments(self.space, list(args_w)))
 
     def funccall_valuestack(self, nargs, frame): # speed hack
+        from pypy.interpreter import gateway
+        from pypy.interpreter.pycode import PyCode
+            
         code = self.getcode() # hook for the jit
         fast_natural_arity = code.fast_natural_arity        
-        if nargs == fast_natural_arity:        
+        if nargs == fast_natural_arity:
             if nargs == 0:
+                assert isinstance(code, gateway.BuiltinCode0)
                 return code.fastcall_0(self.space, self)
             elif nargs == 1:
+                assert isinstance(code, gateway.BuiltinCode1)
                 return code.fastcall_1(self.space, self, frame.peekvalue(0))
             elif nargs == 2:
+                assert isinstance(code, gateway.BuiltinCode2)
                 return code.fastcall_2(self.space, self, frame.peekvalue(1),
                                        frame.peekvalue(0))
             elif nargs == 3:
+                assert isinstance(code, gateway.BuiltinCode3)
                 return code.fastcall_3(self.space, self, frame.peekvalue(2),
                                        frame.peekvalue(1), frame.peekvalue(0))
             elif nargs == 4:
+                assert isinstance(code, gateway.BuiltinCode4)
                 return code.fastcall_4(self.space, self, frame.peekvalue(3),
                                        frame.peekvalue(2), frame.peekvalue(1),
                                         frame.peekvalue(0))
+        elif (nargs|PyCode.FLATPYCALL) == fast_natural_arity:
+            assert isinstance(code, PyCode)
+            return self._flat_pycall(code, nargs, frame)
         elif fast_natural_arity == -1 and nargs >= 1:
-            from pypy.interpreter import gateway
             assert isinstance(code, gateway.BuiltinCodePassThroughArguments1)
             w_obj = frame.peekvalue(nargs-1)
             args = frame.make_arguments(nargs-1)
@@ -104,6 +133,15 @@
             if isinstance(args, ArgumentsFromValuestack):
                 args.frame = None
 
+    def _flat_pycall(self, code, nargs, frame):
+        # code is a PyCode
+        new_frame = self.space.createframe(code, self.w_func_globals,
+                                                   self.closure)
+        for i in xrange(nargs):
+            w_arg = frame.peekvalue(nargs-1-i)
+            new_frame.fastlocals_w[i] = w_arg
+        return new_frame.run()                        
+
     def getdict(self):
         if self.w_func_dict is None:
             self.w_func_dict = self.space.newdict()

Modified: pypy/dist/pypy/interpreter/pycode.py
==============================================================================
--- pypy/dist/pypy/interpreter/pycode.py	(original)
+++ pypy/dist/pypy/interpreter/pycode.py	Sun Sep  7 12:31:55 2008
@@ -108,7 +108,7 @@
                             self._args_as_cellvars.append(-1)   # pad
                         self._args_as_cellvars[i] = j
 
-        self._compute_fastcall()
+        self._compute_flatcall()
 
     co_names = property(lambda self: [self.space.unwrap(w_name) for w_name in self.co_names_w]) # for trace
 
@@ -159,53 +159,20 @@
                       freevars, cellvars, hidden_applevel)
 
     _code_new_w = staticmethod(_code_new_w)
+
+    FLATPYCALL = 0x100
     
-    def _compute_fastcall(self):
+    def _compute_flatcall(self):
         # Speed hack!
         self.fast_natural_arity = -99
-        if not (0 <= self.co_argcount <= 4):
-            return
         if self.co_flags & (CO_VARARGS | CO_VARKEYWORDS):
             return
         if len(self._args_as_cellvars) > 0:
             return
-
-        self.fast_natural_arity = self.co_argcount
-
-    def fastcall_0(self, space, w_func):
-        frame = space.createframe(self, w_func.w_func_globals,
-                                      w_func.closure)
-        return frame.run()
-
-    def fastcall_1(self, space, w_func, w_arg):
-        frame = space.createframe(self, w_func.w_func_globals,
-                                  w_func.closure)
-        frame.fastlocals_w[0] = w_arg # frame.setfastscope([w_arg])
-        return frame.run()
-
-    def fastcall_2(self, space, w_func, w_arg1, w_arg2):
-        frame = space.createframe(self, w_func.w_func_globals,
-                                  w_func.closure)
-        frame.fastlocals_w[0] = w_arg1 # frame.setfastscope([w_arg])
-        frame.fastlocals_w[1] = w_arg2
-        return frame.run()
-
-    def fastcall_3(self, space, w_func, w_arg1, w_arg2, w_arg3):
-        frame = space.createframe(self, w_func.w_func_globals,
-                                  w_func.closure)
-        frame.fastlocals_w[0] = w_arg1 # frame.setfastscope([w_arg])
-        frame.fastlocals_w[1] = w_arg2 
-        frame.fastlocals_w[2] = w_arg3 
-        return frame.run()
-
-    def fastcall_4(self, space, w_func, w_arg1, w_arg2, w_arg3, w_arg4):
-        frame = space.createframe(self, w_func.w_func_globals,
-                                  w_func.closure)
-        frame.fastlocals_w[0] = w_arg1 # frame.setfastscope([w_arg])
-        frame.fastlocals_w[1] = w_arg2 
-        frame.fastlocals_w[2] = w_arg3 
-        frame.fastlocals_w[3] = w_arg4 
-        return frame.run()
+        if self.co_argcount > 0xff:
+            return
+        
+        self.fast_natural_arity = PyCode.FLATPYCALL | self.co_argcount
 
     def funcrun(self, func, args):
         frame = self.space.createframe(self, func.w_func_globals,

Modified: pypy/dist/pypy/interpreter/test/test_function.py
==============================================================================
--- pypy/dist/pypy/interpreter/test/test_function.py	(original)
+++ pypy/dist/pypy/interpreter/test/test_function.py	Sun Sep  7 12:31:55 2008
@@ -457,9 +457,38 @@
         w_meth5 = meth3.descr_method_get(space.wrap('hello'), space.w_str)
         assert space.is_w(w_meth5, w_meth3)
 
-class TestShortcuts(object): 
+class TestShortcuts(object):
 
-    def test_fastcall(self):
+    def test_call_function(self):
+        space = self.space
+        
+        d = {}
+        for i in range(10):
+            args = "(" + ''.join(["a%d," % a for a in range(i)]) + ")"
+            exec """
+def f%s:
+    return %s
+""" % (args, args) in d
+            f = d['f']
+            res = f(*range(i))
+            code = PyCode._from_code(self.space, f.func_code)
+            fn = Function(self.space, code, self.space.newdict())
+
+            assert fn.code.fast_natural_arity == i|PyCode.FLATPYCALL
+            if i < 5:
+
+                 def bomb(*args):
+                     assert False, "shortcutting should have avoided this"
+
+                 code.funcrun = bomb
+                 code.funcrun_obj = bomb
+
+            args_w = map(space.wrap, range(i))            
+            w_res = space.call_function(fn, *args_w)
+            check = space.is_true(space.eq(w_res, space.wrap(res)))
+            assert check
+
+    def test_flatcall(self):
         space = self.space
         
         def f(a):
@@ -467,32 +496,26 @@
         code = PyCode._from_code(self.space, f.func_code)
         fn = Function(self.space, code, self.space.newdict())
 
-        assert fn.code.fast_natural_arity == 1
+        assert fn.code.fast_natural_arity == 1|PyCode.FLATPYCALL
 
-        called = []
-        fastcall_1 = fn.code.fastcall_1
-        def witness_fastcall_1(space, w_func, w_arg):
-            called.append(w_func)
-            return fastcall_1(space, w_func, w_arg)
+        def bomb(*args):
+            assert False, "shortcutting should have avoided this"
 
-        fn.code.fastcall_1 = witness_fastcall_1
+        code.funcrun = bomb
+        code.funcrun_obj = bomb
 
         w_3 = space.newint(3)
         w_res = space.call_function(fn, w_3)
 
         assert w_res is w_3
-        assert called == [fn]
-
-        called = []
 
         w_res = space.appexec([fn, w_3], """(f, x):
         return f(x)
         """)
 
         assert w_res is w_3
-        assert called == [fn]
 
-    def test_fastcall_method(self):
+    def test_flatcall_method(self):
         space = self.space
         
         def f(self, a):
@@ -500,15 +523,13 @@
         code = PyCode._from_code(self.space, f.func_code)
         fn = Function(self.space, code, self.space.newdict())
 
-        assert fn.code.fast_natural_arity == 2
+        assert fn.code.fast_natural_arity == 2|PyCode.FLATPYCALL
 
-        called = []
-        fastcall_2 = fn.code.fastcall_2
-        def witness_fastcall_2(space, w_func, w_arg1, w_arg2):
-            called.append(w_func)
-            return fastcall_2(space, w_func, w_arg1, w_arg2)
+        def bomb(*args):
+            assert False, "shortcutting should have avoided this"
 
-        fn.code.fastcall_2 = witness_fastcall_2
+        code.funcrun = bomb
+        code.funcrun_obj = bomb
 
         w_3 = space.newint(3)
         w_res = space.appexec([fn, w_3], """(f, x):
@@ -521,7 +542,6 @@
         """)
 
         assert space.is_true(w_res)
-        assert called == [fn, fn]       
 
         
         

Modified: pypy/dist/pypy/rpython/memory/gctransform/framework.py
==============================================================================
--- pypy/dist/pypy/rpython/memory/gctransform/framework.py	(original)
+++ pypy/dist/pypy/rpython/memory/gctransform/framework.py	Sun Sep  7 12:31:55 2008
@@ -7,6 +7,7 @@
 from pypy.rpython.memory.gc import marksweep
 from pypy.rpython.memory.gcheader import GCHeaderBuilder
 from pypy.rlib.rarithmetic import ovfcheck
+from pypy.rlib import rstack
 from pypy.rlib.debug import ll_assert
 from pypy.translator.backendopt import graphanalyze
 from pypy.translator.backendopt.support import var_needsgc
@@ -25,7 +26,10 @@
 
     def analyze_direct_call(self, graph, seen=None):
         try:
-            if graph.func._gctransformer_hint_cannot_collect_:
+            func = graph.func
+            if func is rstack.stack_check:
+                return self.translator.config.translation.stackless
+            if func._gctransformer_hint_cannot_collect_:
                 return False
         except AttributeError:
             pass

Modified: pypy/dist/pypy/rpython/memory/gctransform/test/test_framework.py
==============================================================================
--- pypy/dist/pypy/rpython/memory/gctransform/test/test_framework.py	(original)
+++ pypy/dist/pypy/rpython/memory/gctransform/test/test_framework.py	Sun Sep  7 12:31:55 2008
@@ -63,6 +63,28 @@
     gg = graphof(t, g)
     assert CollectAnalyzer(t).analyze_direct_call(gg)
 
+    def g(x):
+        return -x
+    t = rtype(g, [int])
+    gg = graphof(t, g)
+    assert not CollectAnalyzer(t).analyze_direct_call(gg)    
+
+def test_cancollect_stack_check():
+    from pypy.rlib import rstack
+
+    def with_check():
+        rstack.stack_check()
+
+    t = rtype(with_check, [])
+    with_check_graph = graphof(t, with_check)
+
+    assert not t.config.translation.stackless
+    can_collect = CollectAnalyzer(t).analyze_direct_call(with_check_graph)
+    assert not can_collect
+    
+    t.config.translation.stackless = True
+    can_collect = CollectAnalyzer(t).analyze_direct_call(with_check_graph)
+    assert can_collect
 
 class WriteBarrierTransformer(FrameworkGCTransformer):
     initializing_stores = {}

Modified: pypy/dist/pypy/translator/test/test_stackcheck.py
==============================================================================
--- pypy/dist/pypy/translator/test/test_stackcheck.py	(original)
+++ pypy/dist/pypy/translator/test/test_stackcheck.py	Sun Sep  7 12:31:55 2008
@@ -1,22 +1,68 @@
 from pypy import conftest
-from pypy.translator.translator import TranslationContext
+from pypy.translator.translator import TranslationContext, graphof
 from pypy.translator.backendopt.all import backend_optimizations
 from pypy.translator.transform import insert_ll_stackcheck
+from pypy.rpython.memory.gctransform import framework
+from pypy.translator.stackless.transform import StacklessTransformer
 
-def test_simple():
-    class A(object):
-        def __init__(self, n):
-            self.n = n
-            
-    def f(a):
-        x = A(a.n+1)
-        if x.n == 10:
-            return
-        f(x)
+def _follow_path_naive(block, cur_path, accum):
+    cur_path = (cur_path, block)
+    if not block.exits:
+        ops = []
+        while cur_path:
+            block = cur_path[1]
+            ops.extend(reversed(block.operations))
+            cur_path = cur_path[0]
+        accum.append(list(reversed(ops)))
+        return
+    for link in block.exits:
+        _follow_path_naive(link.target, cur_path, accum)
+
+# explodes on loops!
+def paths_naive(g):
+    accum = []
+    _follow_path_naive(g.startblock, None, accum)
+    return accum
 
-    def g(n):
-        f(A(n))
+def direct_target(spaceop):
+    return spaceop.args[0].value._obj.graph.name
+
+def direct_calls(p):
+    names = []
+    for spaceop in p:
+        if spaceop.opname == 'direct_call':
+            names.append(direct_target(spaceop))
+    return names
+
+            
+def check(g, funcname, ignore=None):
+    paths = paths_naive(g)
+    relevant = []
+    for p in paths:
+        funcs_called = direct_calls(p)
+        if funcname in funcs_called and ignore not in funcs_called:
+            assert 'stack_check___' in funcs_called
+            assert (funcs_called.index(funcname) >
+                    funcs_called.index('stack_check___'))
+            relevant.append(p)
+    return relevant
     
+
+class A(object):
+    def __init__(self, n):
+        self.n = n
+
+def f(a):
+    x = A(a.n+1)
+    if x.n == 10:
+        return
+    f(x)
+
+def g(n):
+    f(A(n))
+    return 0
+
+def test_simple():
     t = TranslationContext()
     a = t.buildannotator()
     a.build_types(g, [int])
@@ -29,7 +75,98 @@
     assert n == 1
     if conftest.option.view:
         t.view()
-    
+    check(graphof(t, f), 'f')
 
-    
-    
+def test_gctransformed():
+    t = TranslationContext()
+    a = t.buildannotator()
+    a.build_types(g, [int])
+    a.simplify()
+    t.buildrtyper().specialize()        
+    backend_optimizations(t)
+    t.checkgraphs()
+    n = insert_ll_stackcheck(t)
+    t.checkgraphs()
+    assert n == 1
+    exctransf = t.getexceptiontransformer()
+    f_graph = graphof(t, f)
+    exctransf.create_exception_handling(f_graph)
+    if conftest.option.view:
+        f_graph.show()
+    check(f_graph, 'f')    
+
+    class GCTransform(framework.FrameworkGCTransformer):
+        from pypy.rpython.memory.gc.generation import GenerationGC as \
+                                                          GCClass
+        GC_PARAMS = {}
+
+    gctransf = GCTransform(t)
+    gctransf.transform_graph(f_graph)
+    if conftest.option.view:
+        f_graph.show()
+    relevant = check(f_graph, 'f')        
+    for p in relevant:
+        in_between = False
+        reload = 0
+        for spaceop in p:
+            if spaceop.opname == 'direct_call':
+                target = direct_target(spaceop)
+                if target == 'f':
+                    in_between = False
+                elif target == 'stack_check___':
+                    in_between = True
+            if in_between and spaceop.opname == 'gc_reload_possibly_moved':
+                reload += 1
+                
+        assert reload == 0
+        
+def test_stackless():
+    t = TranslationContext()
+    a = t.buildannotator()
+    a.build_types(g, [int])
+    a.simplify()
+    t.buildrtyper().specialize()        
+    backend_optimizations(t)
+    t.checkgraphs()
+    n = insert_ll_stackcheck(t)
+    t.checkgraphs()
+    assert n == 1
+
+    t.config.translation.stackless = True
+    stacklesstransf = StacklessTransformer(t, g)
+        
+    f_graph = graphof(t, f)
+    stacklesstransf.transform_graph(f_graph)
+    if conftest.option.view:
+        f_graph.show()
+
+    exctransf = t.getexceptiontransformer()
+    exctransf.create_exception_handling(f_graph)
+    if conftest.option.view:
+        f_graph.show()
+    check(f_graph, 'f', 'fetch_retval_void')    
+
+    class GCTransform(framework.FrameworkGCTransformer):
+        from pypy.rpython.memory.gc.generation import GenerationGC as \
+                                                          GCClass
+        GC_PARAMS = {}
+
+    gctransf = GCTransform(t)
+    gctransf.transform_graph(f_graph)
+    if conftest.option.view:
+        f_graph.show()
+    relevant = check(f_graph, 'f', 'fetch_retval_void')
+    for p in relevant:
+        in_between = False
+        reload = 0
+        for spaceop in p:
+            if spaceop.opname == 'direct_call':
+                target = direct_target(spaceop)
+                if target == 'f':
+                    in_between = False
+                elif target == 'stack_check___':
+                    in_between = True
+            if in_between and spaceop.opname == 'gc_reload_possibly_moved':
+                reload += 1
+                
+        assert reload == 1



More information about the Pypy-commit mailing list