[pypy-svn] r4315 - in pypy/trunk/src/pypy: annotation translator translator/test

arigo at codespeak.net arigo at codespeak.net
Fri May 7 18:22:26 CEST 2004


Author: arigo
Date: Fri May  7 18:22:25 2004
New Revision: 4315

Added:
   pypy/trunk/src/pypy/annotation/builtin.py   (contents, props changed)
Modified:
   pypy/trunk/src/pypy/annotation/factory.py
   pypy/trunk/src/pypy/annotation/model.py
   pypy/trunk/src/pypy/annotation/unaryop.py
   pypy/trunk/src/pypy/translator/annrpython.py
   pypy/trunk/src/pypy/translator/test/test_annrpython.py
Log:
Added support for built-in functions and methods. This involved some clean-up
and the creation of a new class Bookkeeper where choices are remembered for
the next reflow.

One test now fails, because I broke the ability for the analysis to process 
calls to other Python functions.



Added: pypy/trunk/src/pypy/annotation/builtin.py
==============================================================================
--- (empty file)
+++ pypy/trunk/src/pypy/annotation/builtin.py	Fri May  7 18:22:25 2004
@@ -0,0 +1,30 @@
+"""
+Built-in functions.
+"""
+
+from pypy.annotation.model import SomeInteger, SomeObject
+from pypy.annotation.factory import ListFactory, getbookkeeper
+
+
+def builtin_len(s_obj):
+    return s_obj.len()
+
+def builtin_range(*args):
+    factory = getbookkeeper().getfactory(ListFactory)
+    factory.generalize(SomeInteger())  # XXX nonneg=...
+    return factory.create()
+
+def builtin_pow(s_base, s_exponent, *args):
+    if s_base.knowntype is s_exponent.knowntype is int:
+        return SomeInteger()
+    else:
+        return SomeObject()
+
+
+# collect all functions
+import __builtin__
+BUILTIN_FUNCTIONS = {}
+for name, value in globals().items():
+    if name.startswith('builtin_'):
+        original = getattr(__builtin__, name[8:])
+        BUILTIN_FUNCTIONS[original] = value

Modified: pypy/trunk/src/pypy/annotation/factory.py
==============================================================================
--- pypy/trunk/src/pypy/annotation/factory.py	(original)
+++ pypy/trunk/src/pypy/annotation/factory.py	Fri May  7 18:22:25 2004
@@ -10,6 +10,7 @@
 from pypy.annotation.pairtype import pair
 from pypy.annotation.model import SomeImpossibleValue, SomeList
 from pypy.annotation.model import SomeObject, SomeInstance
+from pypy.interpreter.miscutils import getthreadlocals
 
 
 class BlockedInference(Exception):
@@ -19,6 +20,71 @@
     def __init__(self, factories = ()):
         # factories that need to be invalidated
         self.invalidatefactories = factories
+        self.position_key = getattr(getbookkeeper(), 'position_key', None)
+
+
+class Bookkeeper:
+    """The log of choices that have been made while analysing the operations.
+    It ensures that the same 'choice objects' will be returned if we ask
+    again during reflowing.  Like ExecutionContext, there is an implicit
+    Bookkeeper that can be obtained from a thread-local variable.
+
+    Currently used for factories and user-defined classes."""
+
+    def __init__(self):
+        self.creationpoints = {} # map positions-in-blocks to Factories
+        self.userclasses = {}    # map classes to ClassDefs
+
+    def enter(self, position_key):
+        """Start of an operation.
+        The operation is uniquely identified by the given key."""
+        self.position_key = position_key
+        self.choice_id = 0
+        getthreadlocals().bookkeeper = self
+
+    def leave(self):
+        """End of an operation."""
+        del getthreadlocals().bookkeeper
+        del self.position_key
+        del self.choice_id
+
+    def nextchoice(self):
+        """Get the next choice key.  The keys are unique, but they follow
+        the same sequence while reflowing."""
+        # 'position_key' is an arbitrary key that identifies a specific
+        # operation, but calling nextchoice() several times during the same
+        # operation returns a different choice key.
+        key = self.position_key, self.choice_id
+        self.choice_id += 1
+        return key
+
+    def getfactory(self, factorycls, *factoryargs):
+        """Get the Factory associated with the current position,
+        or if it doesn't exist yet build it with factorycls(*factoryargs)."""
+        key = self.nextchoice()
+        try:
+            return self.creationpoints[key]
+        except KeyError:
+            factory = factorycls(*factoryargs)
+            factory.position_key = self.position_key
+            self.creationpoints[key] = factory
+            return factory
+
+    def getclassdef(self, cls):
+        """Get the ClassDef associated with the given user cls."""
+        if cls is object:
+            return None
+        try:
+            return self.userclasses[cls]
+        except KeyError:
+            self.userclasses[cls] = ClassDef(cls, self)
+            return self.userclasses[cls]
+
+
+def getbookkeeper():
+    """Get the current Bookkeeper.
+    Only works during the analysis of an operation."""
+    return getthreadlocals().bookkeeper
 
 
 #
@@ -37,8 +103,8 @@
 
 class InstanceFactory:
 
-    def __init__(self, cls, userclasses):
-        self.classdef = getclassdef(cls, userclasses)
+    def __init__(self, classdef):
+        self.classdef = classdef
         self.classdef.instancefactories[self] = True
 
     def create(self):
@@ -48,7 +114,7 @@
 class ClassDef:
     "Wraps a user class."
 
-    def __init__(self, cls, userclasses):
+    def __init__(self, cls, bookkeeper):
         self.attrs = {}          # attrs is updated with new information
         self.revision = 0        # which increases the revision number
         self.instancefactories = {}
@@ -59,7 +125,7 @@
             base = cls.__bases__[0]
         else:
             base = object
-        self.basedef = getclassdef(base, userclasses)
+        self.basedef = bookkeeper.getclassdef(base)
         if self.basedef:
             self.basedef.subdefs[cls] = self
 
@@ -106,13 +172,3 @@
             # bump the revision number of this class and all subclasses
             subdef.revision += 1
         self.attrs[attr] = s_value
-
-
-def getclassdef(cls, cache):
-    if cls is object:
-        return None
-    try:
-        return cache[cls]
-    except KeyError:
-        cache[cls] = ClassDef(cls, cache)
-        return cache[cls]

Modified: pypy/trunk/src/pypy/annotation/model.py
==============================================================================
--- pypy/trunk/src/pypy/annotation/model.py	(original)
+++ pypy/trunk/src/pypy/annotation/model.py	Fri May  7 18:22:25 2004
@@ -28,6 +28,7 @@
 #
 
 
+from types import ClassType, BuiltinFunctionType
 from pypy.annotation.pairtype import pair, extendabletype
 
 
@@ -78,8 +79,13 @@
     knowntype = tuple
     def __init__(self, items):
         self.items = tuple(items)   # tuple of s_xxx elements
-    def len(self):
-        return immutablevalue(len(self.items))
+
+class SomeClass(SomeObject):
+    "Stands for a user-defined class object."
+    # only used when the class object is loaded in a variable
+    knowntype = ClassType
+    def __init__(self, cls):
+        self.cls = cls
 
 class SomeInstance(SomeObject):
     "Stands for an instance of a (user-defined) class."
@@ -88,6 +94,12 @@
         self.knowntype = classdef.cls
         self.revision = classdef.revision
 
+class SomeBuiltin(SomeObject):
+    "Stands for a built-in function or method with special-cased analysis."
+    knowntype = BuiltinFunctionType  # == BuiltinMethodType
+    def __init__(self, analyser):
+        self.analyser = analyser
+
 class SomeImpossibleValue(SomeObject):
     """The empty set.  Instances are placeholders for objects that
     will never show up at run-time, e.g. elements of an empty list."""
@@ -103,6 +115,10 @@
         result = SomeString()
     elif isinstance(x, tuple):
         result = SomeTuple(items = [immutablevalue(e) for e in x])
+    elif x in BUILTIN_FUNCTIONS:
+        result = SomeBuiltin(BUILTIN_FUNCTIONS[x])
+    elif isinstance(x, (type, ClassType)) and x.__module__ != '__builtin__':
+        result = SomeClass(x)
     else:
         result = SomeObject()
     result.const = x
@@ -121,6 +137,18 @@
     else:
         return SomeObject()
 
+def decode_simple_call(s_args, s_kwds):
+    s_nbargs = s_args.len()
+    if not s_nbargs.is_constant():
+        return None
+    nbargs = s_nbargs.const
+    arglist = [pair(s_args, immutablevalue(j)).getitem()
+               for j in range(nbargs)]
+##        nbkwds = self.heap.get(ANN.len, varkwds_cell)
+##        if nbkwds != 0:
+##            return None  # XXX deal with dictionaries with constant keys
+    return arglist
+
 
 # ____________________________________________________________
 # internal
@@ -140,11 +168,12 @@
 
 def missing_operation(cls, name):
     def default_op(*args):
-        print '* warning, no type available for %s(%s)' % (
-            name, ', '.join([repr(a) for a in args]))
+        #print '* warning, no type available for %s(%s)' % (
+        #    name, ', '.join([repr(a) for a in args]))
         return SomeObject()
     setattr(cls, name, default_op)
 
 # this has the side-effect of registering the unary and binary operations
-from pypy.annotation.unaryop import UNARY_OPERATIONS
+from pypy.annotation.unaryop  import UNARY_OPERATIONS
 from pypy.annotation.binaryop import BINARY_OPERATIONS
+from pypy.annotation.builtin  import BUILTIN_FUNCTIONS

Modified: pypy/trunk/src/pypy/annotation/unaryop.py
==============================================================================
--- pypy/trunk/src/pypy/annotation/unaryop.py	(original)
+++ pypy/trunk/src/pypy/annotation/unaryop.py	Fri May  7 18:22:25 2004
@@ -6,12 +6,14 @@
 from pypy.annotation.model import SomeObject, SomeInteger, SomeBool
 from pypy.annotation.model import SomeString, SomeList
 from pypy.annotation.model import SomeTuple, SomeImpossibleValue
-from pypy.annotation.model import SomeInstance
+from pypy.annotation.model import SomeInstance, SomeBuiltin, SomeClass
+from pypy.annotation.model import immutablevalue, decode_simple_call
 from pypy.annotation.model import set, setunion, missing_operation
 from pypy.annotation.factory import BlockedInference
+from pypy.annotation.factory import InstanceFactory, getbookkeeper
 
 
-UNARY_OPERATIONS = set(['len', 'is_true', 'getattr', 'setattr'])
+UNARY_OPERATIONS = set(['len', 'is_true', 'getattr', 'setattr', 'call'])
 
 for opname in UNARY_OPERATIONS:
     missing_operation(SomeObject, opname)
@@ -25,6 +27,26 @@
     def is_true(obj):
         return SomeBool()
 
+    def getattr(obj, attr):
+        # get a SomeBuiltin if the object has a corresponding method
+        if attr.is_constant() and isinstance(attr.const, str):
+            attr = attr.const
+            if hasattr(obj, 'method_' + attr):
+                return SomeBuiltin(getattr(obj, 'method_' + attr))
+        return SomeObject()
+
+
+class __extend__(SomeTuple):
+
+    def len(tup):
+        return immutablevalue(len(tup.items))
+
+
+class __extend__(SomeList):
+
+    def method_append(lst, s_item):
+        pair(lst, SomeInteger()).setitem(s_item)
+
 
 class __extend__(SomeInstance):
 
@@ -64,3 +86,24 @@
             clsdef.generalize(attr, s_value)
             raise BlockedInference(clsdef.getallfactories())
         return SomeObject()
+
+
+class __extend__(SomeBuiltin):
+
+    def call(meth, args, kwds):
+        # decode the arguments and forward the analysis of this builtin
+        arglist = decode_simple_call(args, kwds)
+        if arglist is not None:
+            return meth.analyser(*arglist)
+        else:
+            return SomeObject()
+
+
+class __extend__(SomeClass):
+
+    def call(cls, args, kwds):
+        # XXX flow into __init__
+        bookkeeper = getbookkeeper()
+        classdef = bookkeeper.getclassdef(cls.cls)
+        factory = bookkeeper.getfactory(InstanceFactory, classdef)
+        return factory.create()

Modified: pypy/trunk/src/pypy/translator/annrpython.py
==============================================================================
--- pypy/trunk/src/pypy/translator/annrpython.py	(original)
+++ pypy/trunk/src/pypy/translator/annrpython.py	Fri May  7 18:22:25 2004
@@ -4,7 +4,7 @@
 from pypy.annotation import model as annmodel
 from pypy.annotation.model import pair
 from pypy.annotation.factory import ListFactory, InstanceFactory
-from pypy.annotation.factory import BlockedInference
+from pypy.annotation.factory import BlockedInference, Bookkeeper
 from pypy.objspace.flow.model import Variable, Constant, UndefinedConstant
 from pypy.objspace.flow.model import SpaceOperation
 
@@ -21,9 +21,8 @@
         self.pendingblocks = []  # list of (block, list-of-SomeValues-args)
         self.bindings = {}       # map Variables to SomeValues
         self.annotated = {}      # set of blocks already seen
-        self.creationpoints = {} # map positions-in-blocks to Factories
+        self.bookkeeper = Bookkeeper()
         self.translator = translator
-        self.userclasses = {}    # set of user classes
 
     #___ convenience high-level interface __________________
 
@@ -58,11 +57,11 @@
 
     def getuserclasses(self):
         """Return a set of known user classes."""
-        return self.userclasses
+        return self.bookkeeper.userclasses
 
     def getuserattributes(self, cls):
         """Enumerate the attributes of the given user class, as Variable()s."""
-        clsdef = self.userclasses[cls]
+        clsdef = self.bookkeeper.userclasses[cls]
         for attr, s_value in clsdef.attrs.items():
             v = Variable(name=attr)
             self.bindings[v] = s_value
@@ -159,12 +158,13 @@
                 self.flowin(block)
             except BlockedInference, e:
                 #print '_'*60
-                #print 'Blocked at %r:' % (self.curblockpos,)
+                #print 'Blocked at %r:' % (e.position_key,)
                 #import traceback, sys
                 #traceback.print_tb(sys.exc_info()[2])
                 self.annotated[block] = False   # failed, hopefully temporarily
                 for factory in e.invalidatefactories:
-                    self.reflowpendingblock(factory.block)
+                    oldblock, oldindex = factory.position_key
+                    self.reflowpendingblock(oldblock)
 
     def reflowpendingblock(self, block):
         self.pendingblocks.append((block, None))
@@ -189,27 +189,15 @@
     def flowin(self, block):
         #print 'Flowing', block, [self.binding(a) for a in block.inputargs]
         for i in range(len(block.operations)):
-            self.curblockpos = block, i
-            self.consider_op(block.operations[i])
+            try:
+                self.bookkeeper.enter((block, i))
+                self.consider_op(block.operations[i])
+            finally:
+                self.bookkeeper.leave()
         for link in block.exits:
             cells = [self.binding(a) for a in link.args]
             self.addpendingblock(link.target, cells)
 
-    def getfactory(self, factorycls, *factoryargs):
-        try:
-            factory = self.creationpoints[self.curblockpos]
-        except KeyError:
-            block = self.curblockpos[0]
-            factory = factorycls(*factoryargs)
-            factory.block = block
-            self.creationpoints[self.curblockpos] = factory
-        # self.curblockpos is an arbitrary key that identifies a specific
-        # position, so that asking twice for a factory from the same position
-        # returns the same factory object.  Because we can ask for several
-        # factories in the same operation, we change self.curblockpos here
-        self.curblockpos = self.curblockpos, 'bis'
-        return factory
-
 
     #___ creating the annotations based on operations ______
 
@@ -250,50 +238,42 @@
         return annmodel.SomeTuple(items = args)
 
     def consider_op_newlist(self, *args):
-        factory = self.getfactory(ListFactory)
+        factory = self.bookkeeper.getfactory(ListFactory)
         for a in args:
             factory.generalize(a)
         return factory.create()
 
     def decode_simple_call(self, s_varargs, s_varkwds):
-        s_nbargs = s_varargs.len()
-        if not s_nbargs.is_constant():
-            return None
-        nbargs = s_nbargs.const
-        arg_cells = [pair(s_varargs, annmodel.immutablevalue(j)).getitem()
-                     for j in range(nbargs)]
-##        nbkwds = self.heap.get(ANN.len, varkwds_cell)
-##        if nbkwds != 0:
-##            return None  # XXX deal with dictionaries with constant keys
-        return arg_cells
+        # XXX replace all uses of this with direct calls into annmodel
+        return annmodel.decode_simple_call(s_varargs, s_varkwds)
 
-    def consider_op_call(self, s_func, s_varargs, s_kwargs):
-        if not s_func.is_constant():
-            return annmodel.SomeObject()
-        func = s_func.const
+##    def consider_op_call(self, s_func, s_varargs, s_kwargs):
+##        if not s_func.is_constant():
+##            return annmodel.SomeObject()
+##        func = s_func.const
         
-        # XXX: generalize this later
-        if func is range:
-            factory = self.getfactory(ListFactory)
-            factory.generalize(annmodel.SomeInteger())  # XXX nonneg=...
-            return factory.create()
-        elif func is pow:
-            args = self.decode_simple_call(s_varargs, s_kwargs)
-            if args is not None and len(args) == 2:
-                if (issubclass(args[0].knowntype, int) and
-                    issubclass(args[1].knowntype, int)):
-                    return annmodel.SomeInteger()
-        elif isinstance(func, FunctionType) and self.translator:
-            args = self.decode_simple_call(s_varargs, s_kwargs)
-            return self.translator.consider_call(self, func, args)
-        elif (isinstance(func, (type, ClassType)) and
-              func.__module__ != '__builtin__'):
-            # XXX flow into __init__/__new__
-            factory = self.getfactory(InstanceFactory, func, self.userclasses)
-            return factory.create()
-        elif isinstance(func,type):
-            return annmodel.valueoftype(func)
-        return annmodel.SomeObject()
+##        # XXX: generalize this later
+##        if func is range:
+##            factory = self.getfactory(ListFactory)
+##            factory.generalize(annmodel.SomeInteger())  # XXX nonneg=...
+##            return factory.create()
+##        elif func is pow:
+##            args = self.decode_simple_call(s_varargs, s_kwargs)
+##            if args is not None and len(args) == 2:
+##                if (issubclass(args[0].knowntype, int) and
+##                    issubclass(args[1].knowntype, int)):
+##                    return annmodel.SomeInteger()
+##        elif isinstance(func, FunctionType) and self.translator:
+##            args = self.decode_simple_call(s_varargs, s_kwargs)
+##            return self.translator.consider_call(self, func, args)
+##        elif (isinstance(func, (type, ClassType)) and
+##              func.__module__ != '__builtin__'):
+##            # XXX flow into __init__/__new__
+##            factory = self.getfactory(InstanceFactory, func, self.userclasses)
+##            return factory.create()
+##        elif isinstance(func,type):
+##            return annmodel.valueoftype(func)
+##        return annmodel.SomeObject()
 
 
 ##    def consider_op_setattr(self,obj,attr,newval):

Modified: pypy/trunk/src/pypy/translator/test/test_annrpython.py
==============================================================================
--- pypy/trunk/src/pypy/translator/test/test_annrpython.py	(original)
+++ pypy/trunk/src/pypy/translator/test/test_annrpython.py	Fri May  7 18:22:25 2004
@@ -182,6 +182,16 @@
                               annmodel.SomeObject()
                               ]))
 
+    def test_poor_man_range(self):
+        translator = Translator(snippet.poor_man_range)
+        graph = translator.getflowgraph()
+        a = RPythonAnnotator(translator)
+        a.build_types(graph, [int])
+        # result should be a list of integers
+        self.assertEquals(a.gettype(graph.getreturnvar()), list)
+        end_cell = a.binding(graph.getreturnvar())
+        self.assertEquals(end_cell.s_item.knowntype, int)
+
 
 def g(n):
     return [0,1,2,n]


More information about the Pypy-commit mailing list