[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