[pypy-svn] r68910 - in pypy/trunk/pypy: interpreter interpreter/test module/pypyjit/test objspace objspace/std

cfbolz at codespeak.net cfbolz at codespeak.net
Mon Nov 2 14:21:56 CET 2009


Author: cfbolz
Date: Mon Nov  2 14:21:55 2009
New Revision: 68910

Modified:
   pypy/trunk/pypy/interpreter/function.py
   pypy/trunk/pypy/interpreter/gateway.py
   pypy/trunk/pypy/interpreter/mixedmodule.py
   pypy/trunk/pypy/interpreter/test/test_function.py
   pypy/trunk/pypy/module/pypyjit/test/test_pypy_c.py
   pypy/trunk/pypy/objspace/descroperation.py
   pypy/trunk/pypy/objspace/std/callmethod.py
Log:
Prevent the changing of the func_code attribute of builtin functions. This makes
some shortcuts less beautiful, but produces one guard and one getfield less
a bit everywhere.


Modified: pypy/trunk/pypy/interpreter/function.py
==============================================================================
--- pypy/trunk/pypy/interpreter/function.py	(original)
+++ pypy/trunk/pypy/interpreter/function.py	Mon Nov  2 14:21:55 2009
@@ -16,12 +16,20 @@
 
 funccallunrolling = unrolling_iterable(range(4))
 
+ at jit.purefunction
+def _get_immutable_code(func):
+    assert not func.can_change_code
+    return func.code
+
 class Function(Wrappable):
     """A function is a code object captured with some environment:
     an object space, a dictionary of globals, default arguments,
     and an arbitrary 'closure' passed to the code object."""
 
-    def __init__(self, space, code, w_globals=None, defs_w=[], closure=None, forcename=None):
+    can_change_code = True
+
+    def __init__(self, space, code, w_globals=None, defs_w=[], closure=None,
+                 forcename=None):
         self.space = space
         self.name = forcename or code.co_name
         self.w_doc = None   # lazily read from code.getdocstring()
@@ -48,7 +56,12 @@
         return self.getcode().funcrun_obj(self, w_obj, args)
 
     def getcode(self):
-        return jit.hint(self.code, promote=True)
+        if jit.we_are_jitted():
+            if not self.can_change_code:
+                self = jit.hint(self, promote=True)
+                return _get_immutable_code(self)
+            return jit.hint(self.code, promote=True)
+        return self.code
     
     def funccall(self, *args_w): # speed hack
         from pypy.interpreter import gateway
@@ -368,6 +381,9 @@
 
     def fset_func_code(space, self, w_code):
         from pypy.interpreter.pycode import PyCode
+        if not self.can_change_code:
+            raise OperationError(space.w_TypeError,
+                    space.wrap("Cannot change code attribute of builtin functions"))
         code = space.interp_w(Code, w_code)
         closure_len = 0
         if self.closure:
@@ -568,7 +584,11 @@
                                  "'%s' object is not callable" % typename))
         return space.wrap(ClassMethod(w_function))
 
+class FunctionWithFixedCode(Function):
+    can_change_code = False
+
 class BuiltinFunction(Function):
+    can_change_code = False
 
     def __init__(self, func):
         assert isinstance(func, Function)

Modified: pypy/trunk/pypy/interpreter/gateway.py
==============================================================================
--- pypy/trunk/pypy/interpreter/gateway.py	(original)
+++ pypy/trunk/pypy/interpreter/gateway.py	Mon Nov  2 14:21:55 2009
@@ -16,6 +16,7 @@
 from pypy.interpreter.error import OperationError 
 from pypy.interpreter import eval
 from pypy.interpreter.function import Function, Method, ClassMethod
+from pypy.interpreter.function import FunctionWithFixedCode
 from pypy.interpreter.baseobjspace import W_Root, ObjSpace, Wrappable
 from pypy.interpreter.baseobjspace import Wrappable, SpaceCache, DescrMismatch
 from pypy.interpreter.argument import Arguments, Signature
@@ -788,7 +789,7 @@
         space = cache.space
         defs = gateway._getdefaults(space) # needs to be implemented by subclass
         code = gateway._code
-        fn = Function(space, code, None, defs, forcename = gateway.name)
+        fn = FunctionWithFixedCode(space, code, None, defs, forcename = gateway.name)
         if not space.config.translating: # for tests and py.py
             fn._freeze_()
         if gateway.as_classmethod:

Modified: pypy/trunk/pypy/interpreter/mixedmodule.py
==============================================================================
--- pypy/trunk/pypy/interpreter/mixedmodule.py	(original)
+++ pypy/trunk/pypy/interpreter/mixedmodule.py	Mon Nov  2 14:21:55 2009
@@ -56,7 +56,14 @@
                 #print "loaded", w_value 
                 # obscure
                 func = space.interpclass_w(w_value)
-                if type(func) is Function:
+                # the idea of the following code is that all functions that are
+                # directly in a mixed-module are "builtin", e.g. they get a
+                # special type without a __get__
+                # note that this is not just all functions that contain a
+                # builtin code object, as e.g. methods of builtin types have to
+                # be normal Functions to get the correct binding behaviour
+                if (isinstance(func, Function) and
+                    type(func) is not BuiltinFunction):
                     try:
                         bltin = func._builtinversion_
                     except AttributeError:

Modified: pypy/trunk/pypy/interpreter/test/test_function.py
==============================================================================
--- pypy/trunk/pypy/interpreter/test/test_function.py	(original)
+++ pypy/trunk/pypy/interpreter/test/test_function.py	Mon Nov  2 14:21:55 2009
@@ -84,7 +84,12 @@
             return f() # a closure
         raises(ValueError, "f.func_code = h.func_code")
 
-        
+    def test_write_code_builtin_forbidden(self):
+        def f(*args):
+            return 42
+            raises(TypeError, "dir.func_code = f.func_code")
+            raises(TypeError, "list.append.im_func.func_code = f.func_code") 
+
 
 class AppTestFunction: 
     def test_simple_call(self):

Modified: pypy/trunk/pypy/module/pypyjit/test/test_pypy_c.py
==============================================================================
--- pypy/trunk/pypy/module/pypyjit/test/test_pypy_c.py	(original)
+++ pypy/trunk/pypy/module/pypyjit/test/test_pypy_c.py	Mon Nov  2 14:21:55 2009
@@ -252,8 +252,7 @@
         assert len(callisinstance.get_opnames("guard")) <= 2
 
         bytecode, = self.get_by_bytecode("STORE_ATTR")
-        # XXX where does that come from?
-        assert bytecode.get_opnames() == ["getfield_gc", "guard_value"]
+        assert bytecode.get_opnames() == []
 
     def test_mixed_type_loop(self):
         self.run_source('''
@@ -272,7 +271,28 @@
         bytecode, = self.get_by_bytecode("BINARY_ADD")
         assert not bytecode.get_opnames("call")
         assert not bytecode.get_opnames("new")
-        assert len(bytecode.get_opnames("guard")) <= 3
+        assert len(bytecode.get_opnames("guard")) <= 2
+
+    def test_call_builtin_function(self):
+        self.run_source('''
+            class A(object):
+                pass
+            def main(n):
+                i = 2
+                l = []
+                while i < n:
+                    i += 1
+                    l.append(i)
+                return i, len(l)
+        ''',
+                   ([20], (20, 18)),
+                   ([31], (31, 29)))
+
+        bytecode, = self.get_by_bytecode("CALL_METHOD")
+        assert len(bytecode.get_opnames("new_with_vtable")) == 1 # the forcing of the int
+        assert len(bytecode.get_opnames("call")) == 1 # the call to append
+        assert len(bytecode.get_opnames("guard")) == 1 # guard_no_exception after the call
+
 
 class AppTestJIT(PyPyCJITTests):
     def setup_class(cls):
@@ -288,7 +308,7 @@
 class TestJIT(PyPyCJITTests):
     def setup_class(cls):
         if option.pypy_c is None:
-            py.test.skip("pass --pypy-c!")
+            py.test.skip("pass --pypy!")
         cls.tmpdir = udir.join('pypy-jit')
         cls.tmpdir.ensure(dir=1)
         cls.counter = 0

Modified: pypy/trunk/pypy/objspace/descroperation.py
==============================================================================
--- pypy/trunk/pypy/objspace/descroperation.py	(original)
+++ pypy/trunk/pypy/objspace/descroperation.py	Mon Nov  2 14:21:55 2009
@@ -1,7 +1,7 @@
 import operator
 from pypy.interpreter.error import OperationError
 from pypy.interpreter.baseobjspace import ObjSpace
-from pypy.interpreter.function import Function, Method
+from pypy.interpreter.function import Function, Method, FunctionWithFixedCode
 from pypy.interpreter.argument import Arguments
 from pypy.interpreter.typedef import default_identity_hash
 from pypy.tool.sourcetools import compile2, func_with_new_name
@@ -81,7 +81,7 @@
     def get_and_call_args(space, w_descr, w_obj, args):
         descr = space.interpclass_w(w_descr)
         # a special case for performance and to avoid infinite recursion
-        if type(descr) is Function:
+        if isinstance(descr, Function):
             return descr.call_obj_args(w_obj, args)
         else:
             w_impl = space.get(w_descr, w_obj)
@@ -89,8 +89,15 @@
 
     def get_and_call_function(space, w_descr, w_obj, *args_w):
         descr = space.interpclass_w(w_descr)
+        typ = type(descr)
         # a special case for performance and to avoid infinite recursion
-        if type(descr) is Function:
+        if typ is Function or typ is FunctionWithFixedCode:
+            # isinstance(typ, Function) would not be correct here:
+            # for a BuiltinFunction we must not use that shortcut, because a
+            # builtin function binds differently than a normal function
+            # see test_builtin_as_special_method_is_not_bound
+            # in interpreter/test/test_function.py
+
             # the fastcall paths are purely for performance, but the resulting
             # increase of speed is huge
             return descr.funccall(w_obj, *args_w)

Modified: pypy/trunk/pypy/objspace/std/callmethod.py
==============================================================================
--- pypy/trunk/pypy/objspace/std/callmethod.py	(original)
+++ pypy/trunk/pypy/objspace/std/callmethod.py	Mon Nov  2 14:21:55 2009
@@ -55,14 +55,16 @@
             # this handles directly the common case
             #   module.function(args..)
             w_value = w_obj.getdictvalue(space, w_name)
-        elif type(w_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
+        else:
+            typ = type(w_descr)
+            if typ is function.Function or typ is function.FunctionWithFixedCode:
+                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)
@@ -89,7 +91,8 @@
     w_getattribute = space.lookup(w_obj, '__getattribute__')
     if w_getattribute is object_getattribute(space):
         w_descr = space.lookup(w_obj, methname)
-        if type(w_descr) is function.Function:
+        typ = type(w_descr)
+        if typ is function.Function or typ is function.FunctionWithFixedCode:
             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,



More information about the Pypy-commit mailing list