[pypy-svn] r39482 - in pypy/dist: lib-python/modified-2.4.1 pypy/config pypy/interpreter pypy/interpreter/astcompiler pypy/interpreter/test

arigo at codespeak.net arigo at codespeak.net
Tue Feb 27 09:20:54 CET 2007


Author: arigo
Date: Tue Feb 27 09:20:52 2007
New Revision: 39482

Added:
   pypy/dist/pypy/interpreter/callmethod.py   (contents, props changed)
   pypy/dist/pypy/interpreter/test/test_callmethod.py   (contents, props changed)
Modified:
   pypy/dist/lib-python/modified-2.4.1/opcode.py
   pypy/dist/pypy/config/pypyoption.py
   pypy/dist/pypy/interpreter/astcompiler/pyassem.py
   pypy/dist/pypy/interpreter/astcompiler/pycodegen.py
   pypy/dist/pypy/interpreter/baseobjspace.py
Log:
Two bytecodes to try to speed up method calls.
See callmethod.py for a description.

The goal is to avoid creating the bound method object in the common
case.  Only works for keyword-less, *arg-less, **arg-less calls so far
but it would be easy to extend.



Modified: pypy/dist/lib-python/modified-2.4.1/opcode.py
==============================================================================
--- pypy/dist/lib-python/modified-2.4.1/opcode.py	(original)
+++ pypy/dist/lib-python/modified-2.4.1/opcode.py	Tue Feb 27 09:20:52 2007
@@ -189,5 +189,7 @@
 
 # pypy modification, experimental bytecode
 def_op('CALL_LIKELY_BUILTIN', 144)    # #args + (#kwargs << 8)
+def_op('LOOKUP_METHOD', 145)          # Index in name list
+def_op('CALL_METHOD', 146)            # #args not including 'self'
 
 del def_op, name_op, jrel_op, jabs_op

Modified: pypy/dist/pypy/config/pypyoption.py
==============================================================================
--- pypy/dist/pypy/config/pypyoption.py	(original)
+++ pypy/dist/pypy/config/pypyoption.py	Tue Feb 27 09:20:52 2007
@@ -58,7 +58,10 @@
         BoolOption("CALL_LIKELY_BUILTIN", "emit a special bytecode for likely calls to builtin functions",
                    default=False,
                    requires=[("objspace.usepycfiles", False),
-                             ("objspace.std.withmultidict", True)])
+                             ("objspace.std.withmultidict", True)]),
+        BoolOption("CALL_METHOD", "emit a special bytecode for expr.name()",
+                   default=False,
+                   requires=[("objspace.usepycfiles", False)]),
         ]),
 
     BoolOption("nofaking", "disallow faking in the object space",

Modified: pypy/dist/pypy/interpreter/astcompiler/pyassem.py
==============================================================================
--- pypy/dist/pypy/interpreter/astcompiler/pyassem.py	(original)
+++ pypy/dist/pypy/interpreter/astcompiler/pyassem.py	Tue Feb 27 09:20:52 2007
@@ -745,6 +745,7 @@
     _convert_LOAD_GLOBAL = _convert_NAME
     _convert_STORE_GLOBAL = _convert_NAME
     _convert_DELETE_GLOBAL = _convert_NAME
+    _convert_LOOKUP_METHOD = _convert_NAME
 
     def _convert_DEREF(self, inst):
         assert isinstance(inst, InstrName)
@@ -938,6 +939,8 @@
     return depth_CALL_FUNCTION(argc)-1
 def depth_CALL_FUNCTION_VAR_KW(argc):
     return depth_CALL_FUNCTION(argc)-2
+def depth_CALL_METHOD(argc):
+    return -argc-1
 def depth_MAKE_FUNCTION(argc):
     return -argc
 def depth_MAKE_CLOSURE(argc):
@@ -1039,6 +1042,7 @@
         'SETUP_FINALLY': 3,
         'FOR_ITER': 1,
         'WITH_CLEANUP': 3,
+        'LOOKUP_METHOD': 1,
         }
     # use pattern match
     patterns = [

Modified: pypy/dist/pypy/interpreter/astcompiler/pycodegen.py
==============================================================================
--- pypy/dist/pypy/interpreter/astcompiler/pycodegen.py	(original)
+++ pypy/dist/pypy/interpreter/astcompiler/pycodegen.py	Tue Feb 27 09:20:52 2007
@@ -1008,11 +1008,13 @@
         self.emit('EXEC_STMT')
 
     def visitCallFunc(self, node):
+        self.set_lineno(node)
         if self.emit_builtin_call(node):
             return
+        if self.emit_method_call(node):
+            return
         pos = 0
         kw = 0
-        self.set_lineno(node)
         node.node.accept( self )
         for arg in node.args:
             arg.accept( self )
@@ -1029,18 +1031,20 @@
         opcode = callfunc_opcode_info[ have_star*2 + have_dstar]
         self.emitop_int(opcode, kw << 8 | pos)
 
-    def emit_builtin_call(self, node):
-        if not self.space.config.objspace.opcodes.CALL_LIKELY_BUILTIN:
-            return False
+    def check_simple_call_args(self, node):
         if node.star_args is not None or node.dstar_args is not None:
             return False
-        pos = 0
         # check for kw args
         for arg in node.args:
             if isinstance(arg, ast.Keyword):
                 return False
-            else:
-                pos = pos + 1
+        return True
+
+    def emit_builtin_call(self, node):
+        if not self.space.config.objspace.opcodes.CALL_LIKELY_BUILTIN:
+            return False
+        if not self.check_simple_call_args(node):
+            return False
         func = node.node
         if not isinstance(func, ast.Name):
             return False
@@ -1054,10 +1058,25 @@
             and index != -1):
             for arg in node.args:
                 arg.accept(self)
-            self.emitop_int("CALL_LIKELY_BUILTIN", index << 8 | pos)
+            self.emitop_int("CALL_LIKELY_BUILTIN", index << 8 | len(node.args))
             return True
         return False
 
+    def emit_method_call(self, node):
+        if not self.space.config.objspace.opcodes.CALL_METHOD:
+            return False
+        meth = node.node
+        if not isinstance(meth, ast.Getattr):
+            return False
+        if not self.check_simple_call_args(node):
+            return False
+        meth.expr.accept(self)
+        self.emitop('LOOKUP_METHOD', self.mangle(meth.attrname))
+        for arg in node.args:
+            arg.accept(self)
+        self.emitop_int('CALL_METHOD', len(node.args))
+        return True
+
     def visitPrint(self, node):
         self.set_lineno(node)
         if node.dest:

Modified: pypy/dist/pypy/interpreter/baseobjspace.py
==============================================================================
--- pypy/dist/pypy/interpreter/baseobjspace.py	(original)
+++ pypy/dist/pypy/interpreter/baseobjspace.py	Tue Feb 27 09:20:52 2007
@@ -179,6 +179,8 @@
 
         # import extra modules for side-effects, possibly based on config
         import pypy.interpreter.nestedscope     # register *_DEREF bytecodes
+        if self.config.objspace.opcodes.CALL_METHOD:
+            import pypy.interpreter.callmethod  # register *_METHOD bytecodes
 
         self.interned_strings = {}
         self.pending_actions = []

Added: pypy/dist/pypy/interpreter/callmethod.py
==============================================================================
--- (empty file)
+++ pypy/dist/pypy/interpreter/callmethod.py	Tue Feb 27 09:20:52 2007
@@ -0,0 +1,63 @@
+"""
+Two bytecodes to speed up method calls.  Here is how a method call looks
+like: (on the left, without the new bytecodes; on the right, with them)
+
+    <push self>                    <push self>
+    LOAD_ATTR       name           LOOKUP_METHOD   name
+    <push arg 0>                   <push arg 0>
+    ...                            ...
+    <push arg n-1>                 <push arg n-1>
+    CALL_FUNCTION   n              CALL_METHOD     n
+"""
+
+from pypy.interpreter import pyframe, function
+
+
+class __extend__(pyframe.PyFrame):
+
+    def LOOKUP_METHOD(f, nameindex, *ignored):
+        #   stack before                 after
+        #  --------------    --fast-method----fallback-case------------
+        #
+        #                      w_object       None
+        #    w_object    =>    w_function     w_boundmethod_or_whatever
+        #   (more stuff)      (more stuff)   (more stuff)
+        #
+        # NB. this assumes a space based on pypy.objspace.descroperation.
+        space = f.space
+        w_obj = f.popvalue()
+        w_name = f.getname_w(nameindex)
+        w_typ = space.type(w_obj)
+        w_src, w_dummy = space.lookup_in_type_where(w_typ, '__getattribute__')
+        w_value = None
+        if space.is_w(w_src, space.w_object):
+            name = space.str_w(w_name)
+            w_descr = space.lookup(w_obj, name)
+            descr = space.interpclass_w(w_descr)
+            if descr is None:
+                # this handles directly the common case
+                #   module.function(args..)
+                w_value = w_obj.getdictvalue(space, w_name)
+            elif type(descr) is function.Function:
+                w_value = w_obj.getdictvalue_attr_is_in_class(space, w_name)
+                if w_value is None:
+                    # fast method path: a function object in the class,
+                    # nothing in the instance
+                    f.pushvalue(w_descr)
+                    f.pushvalue(w_obj)
+                    return
+        if w_value is None:
+            w_value = space.getattr(w_obj, w_name)
+        f.pushvalue(w_value)
+        f.pushvalue(None)
+
+    def CALL_METHOD(f, nargs, *ignored):
+        # 'nargs' is the argument count excluding the implicit 'self'
+        w_self     = f.peekvalue(nargs)
+        w_callable = f.peekvalue(nargs + 1)
+        try:
+            n = nargs + (w_self is not None)
+            w_result = f.space.call_valuestack(w_callable, n, f)
+        finally:
+            f.dropvalues(nargs + 2)
+        f.pushvalue(w_result)

Added: pypy/dist/pypy/interpreter/test/test_callmethod.py
==============================================================================
--- (empty file)
+++ pypy/dist/pypy/interpreter/test/test_callmethod.py	Tue Feb 27 09:20:52 2007
@@ -0,0 +1,105 @@
+from pypy.conftest import gettestobjspace
+
+
+class AppTestCallMethod:
+    # The exec hacking is needed to have the code snippets compiled
+    # by our own compiler, not CPython's
+
+    def setup_class(cls):
+        cls.space = gettestobjspace(**{"objspace.opcodes.CALL_METHOD": True})
+
+    def test_call_method(self):
+        exec """if 1:
+            class C(object):
+                def m(*args, **kwds):
+                    return args, kwds
+                sm = staticmethod(m)
+                cm = classmethod(m)
+
+            c = C()
+            assert c.m() == ((c,), {})
+            assert c.sm() == ((), {})
+            assert c.cm() == ((C,), {})
+            assert c.m(5) == ((c, 5), {})
+            assert c.sm(6) == ((6,), {})
+            assert c.cm(7) == ((C, 7), {})
+            assert c.m(5, x=3) == ((c, 5), {'x': 3})
+            assert c.m(*range(5)) == ((c, 0, 1, 2, 3, 4), {})
+            assert c.m(**{'u': 4}) == ((c,), {'u': 4})
+        """
+
+    def test_call_attribute(self):
+        exec """if 1:
+            class C(object):
+                def m(*args, **kwds):
+                    return args, kwds
+            def myfunc(*args):
+                return args
+
+            c = C()
+            c.m = c.m2 = myfunc
+            assert c.m() == ()
+            assert c.m(5, 2) == (5, 2)
+            assert c.m2(4) == (4,)
+            assert c.m2("h", "e", "l", "l", "o") == tuple("hello")
+        """
+
+    def test_call_module(self):
+        exec """if 1:
+            import sys
+            try:
+                sys.exit(5)
+            except SystemExit, e:
+                assert e.args == (5,)
+            else:
+                raise Exception, "did not raise?"
+        """
+
+    def test_custom_getattr(self):
+        exec """if 1:
+            class C(object):
+                def __getattr__(self, name):
+                    if name == 'bla':
+                        return myfunc
+                    raise AttributeError
+            def myfunc(*args):
+                return args
+
+            c = C()
+            assert c.bla(1, 2, 3) == (1, 2, 3)
+        """ in {}
+
+    def test_custom_getattribute(self):
+        exec """if 1:
+            class C(object):
+                def __getattribute__(self, name):
+                    if name == 'bla':
+                        return myfunc
+                    raise AttributeError
+                def bla(self):
+                    Boom
+            def myfunc(*args):
+                return args
+
+            c = C()
+            assert c.bla(1, 2, 3) == (1, 2, 3)
+        """ in {}
+
+    def test_builtin(self):
+        exec """if 1:
+            class C(object):
+                foobar = len
+            c = C()
+            assert c.foobar("hello") == 5
+        """
+
+    def test_attributeerror(self):
+        exec """if 1:
+            assert 5 .__add__(6) == 11
+            try:
+                6 .foobar(7)
+            except AttributeError:
+                pass
+            else:
+                raise Exception("did not raise?")
+        """



More information about the Pypy-commit mailing list