Author: Ronan Lamy <ronan.l...@gmail.com> Branch: Changeset: r65342:7ba260941669 Date: 2013-07-11 13:06 +0200 http://bitbucket.org/pypy/pypy/changeset/7ba260941669/
Log: Merge branch flowoperators diff --git a/rpython/config/translationoption.py b/rpython/config/translationoption.py --- a/rpython/config/translationoption.py +++ b/rpython/config/translationoption.py @@ -170,7 +170,6 @@ cmdline="--make-jobs", default=detect_number_of_processors()), # Flags of the TranslationContext: - BoolOption("simplifying", "Simplify flow graphs", default=True), BoolOption("list_comprehension_operations", "When true, look for and special-case the sequence of " "operations that results from a list comprehension and " diff --git a/rpython/flowspace/flowcontext.py b/rpython/flowspace/flowcontext.py --- a/rpython/flowspace/flowcontext.py +++ b/rpython/flowspace/flowcontext.py @@ -14,7 +14,7 @@ recursively_flatten) from rpython.flowspace.specialcase import (rpython_print_item, rpython_print_newline) -from rpython.flowspace.operation import implicit_exceptions +from rpython.flowspace.operation import op class FlowingError(Exception): @@ -55,25 +55,44 @@ pass class SpamBlock(Block): - # make slots optional, for debugging - if hasattr(Block, '__slots__'): - __slots__ = "dead framestate".split() - def __init__(self, framestate): Block.__init__(self, framestate.getvariables()) self.framestate = framestate self.dead = False + def make_recorder(self): + return BlockRecorder(self) + class EggBlock(Block): - # make slots optional, for debugging - if hasattr(Block, '__slots__'): - __slots__ = "prevblock booloutcome last_exception".split() - def __init__(self, inputargs, prevblock, booloutcome): Block.__init__(self, inputargs) self.prevblock = prevblock self.booloutcome = booloutcome + @property + def ancestor(self): + parent = self.prevblock + while isinstance(parent, EggBlock): + parent = parent.prevblock + return parent + + @property + def dead(self): + return self.ancestor.dead + + @property + def framestate(self): + return self.ancestor.framestate + + def make_recorder(self): + recorder = BlockRecorder(self) + curr = self + while isinstance(curr, EggBlock): + prev = curr.prevblock + recorder = Replayer(prev, curr.booloutcome, recorder) + curr = prev + return recorder + def extravars(self, last_exception=None, last_exc_value=None): self.last_exception = last_exception @@ -209,6 +228,7 @@ w_exc_cls, w_exc_value = egg.inputargs[-2:] if isinstance(egg.last_exception, Constant): w_exc_cls = egg.last_exception + assert not isinstance(w_exc_cls.value, list) raise ImplicitOperationError(w_exc_cls, w_exc_value) # ____________________________________________________________ @@ -430,44 +450,23 @@ self.last_instr = state.next_instr self.blockstack = state.blocklist[:] - def recording(self, block): - """ Setup recording of the block and return the recorder. """ - parentblocks = [] - parent = block - while isinstance(parent, EggBlock): - parent = parent.prevblock - parentblocks.append(parent) - # parentblocks = [Egg, Egg, ..., Egg, Spam] not including block - if parent.dead: - raise StopFlowing - self.setstate(parent.framestate) - recorder = BlockRecorder(block) - prevblock = block - for parent in parentblocks: - recorder = Replayer(parent, prevblock.booloutcome, recorder) - prevblock = parent - return recorder + def guessbool(self, w_condition, **kwds): + return self.recorder.guessbool(self, w_condition, **kwds) - def record(self, spaceop): - """Record an operation into the active block""" + def do_operation(self, name, *args_w): recorder = self.recorder if getattr(recorder, 'final_state', None) is not None: self.mergeblock(recorder.crnt_block, recorder.final_state) raise StopFlowing - recorder.append(spaceop) - - def guessbool(self, w_condition, **kwds): - return self.recorder.guessbool(self, w_condition, **kwds) - - def do_operation(self, name, *args_w): spaceop = SpaceOperation(name, args_w, Variable()) spaceop.offset = self.last_instr - self.record(spaceop) + recorder.append(spaceop) return spaceop.result def do_operation_with_implicit_exceptions(self, name, *args_w): w_result = self.do_operation(name, *args_w) - self.handle_implicit_exceptions(implicit_exceptions.get(name)) + oper = getattr(op, name) + self.handle_implicit_exceptions(oper.canraise) return w_result def handle_implicit_exceptions(self, exceptions): @@ -488,39 +487,44 @@ self.pendingblocks = collections.deque([graph.startblock]) while self.pendingblocks: block = self.pendingblocks.popleft() - try: - self.recorder = self.recording(block) - while True: - self.last_instr = self.handle_bytecode(self.last_instr) - self.recorder.final_state = self.getstate() + if not block.dead: + self.record_block(block) - except ImplicitOperationError, e: - if isinstance(e.w_type, Constant): - exc_cls = e.w_type.value - else: - exc_cls = Exception - msg = "implicit %s shouldn't occur" % exc_cls.__name__ - w_type = Constant(AssertionError) - w_value = Constant(AssertionError(msg)) - link = Link([w_type, w_value], graph.exceptblock) - self.recorder.crnt_block.closeblock(link) + def record_block(self, block): + self.setstate(block.framestate) + self.recorder = block.make_recorder() + try: + while True: + self.last_instr = self.handle_bytecode(self.last_instr) + self.recorder.final_state = self.getstate() - except FSException, e: - if e.w_type is self.space.w_ImportError: - msg = 'import statement always raises %s' % e - raise ImportError(msg) - link = Link([e.w_type, e.w_value], graph.exceptblock) - self.recorder.crnt_block.closeblock(link) + except ImplicitOperationError, e: + if isinstance(e.w_type, Constant): + exc_cls = e.w_type.value + else: + exc_cls = Exception + msg = "implicit %s shouldn't occur" % exc_cls.__name__ + w_type = Constant(AssertionError) + w_value = Constant(AssertionError(msg)) + link = Link([w_type, w_value], self.graph.exceptblock) + self.recorder.crnt_block.closeblock(link) - except StopFlowing: - pass + except FSException, e: + if e.w_type == self.space.w_ImportError: + msg = 'import statement always raises %s' % e + raise ImportError(msg) + link = Link([e.w_type, e.w_value], self.graph.exceptblock) + self.recorder.crnt_block.closeblock(link) - except Return as exc: - w_result = exc.value - link = Link([w_result], graph.returnblock) - self.recorder.crnt_block.closeblock(link) + except StopFlowing: + pass - del self.recorder + except Return as exc: + w_result = exc.value + link = Link([w_result], self.graph.returnblock) + self.recorder.crnt_block.closeblock(link) + + self.recorder = None def mergeblock(self, currentblock, currentstate): next_instr = currentstate.next_instr @@ -661,8 +665,8 @@ self.last_exception = operr raise operr else: - raise FSException(space.w_TypeError, - space.wrap("raise: no active exception to re-raise")) + raise space.exc_wrap(TypeError( + "raise: no active exception to re-raise")) w_value = space.w_None if nbargs >= 3: diff --git a/rpython/flowspace/model.py b/rpython/flowspace/model.py --- a/rpython/flowspace/model.py +++ b/rpython/flowspace/model.py @@ -3,6 +3,7 @@ # # the below object/attribute model evolved from # a discussion in Berlin, 4th of october 2003 +import types import py from rpython.tool.uid import uid, Hashable @@ -261,6 +262,7 @@ dummyname = 'v' namesdict = {dummyname : (dummyname, 0)} + @property def name(self): _name = self._name _nr = self._nr @@ -270,11 +272,10 @@ _nr = self._nr = nd[_name][1] nd[_name] = (_name, _nr + 1) return "%s%d" % (_name, _nr) - name = property(name) + @property def renamed(self): return self._name is not self.dummyname - renamed = property(renamed) def __init__(self, name=None): self._name = self.dummyname @@ -314,6 +315,9 @@ self._name = intern(name) self._nr = nr + def foldable(self): + return False + class Constant(Hashable): __slots__ = ["concretetype"] @@ -323,6 +327,25 @@ if concretetype is not None: self.concretetype = concretetype + def foldable(self): + to_check = self.value + if hasattr(to_check, 'im_self'): + to_check = to_check.im_self + if isinstance(to_check, (type, types.ClassType, types.ModuleType)): + # classes/types/modules are assumed immutable + return True + if (hasattr(to_check, '__class__') and + to_check.__class__.__module__ == '__builtin__'): + # builtin object + return True + # User-created instance + if hasattr(to_check, '_freeze_'): + assert to_check._freeze_() is True + return True + else: + # cannot count on it not mutating at runtime! + return False + class UnwrapException(Exception): """Attempted to unwrap a Variable.""" diff --git a/rpython/flowspace/objspace.py b/rpython/flowspace/objspace.py --- a/rpython/flowspace/objspace.py +++ b/rpython/flowspace/objspace.py @@ -19,7 +19,7 @@ from rpython.flowspace.pygraph import PyGraph from rpython.flowspace.specialcase import SPECIAL_CASES from rpython.rlib.unroll import unrolling_iterable, _unroller -from rpython.rlib import rstackovf, rarithmetic +from rpython.rlib import rstackovf from rpython.rlib.rarithmetic import is_valid_int @@ -45,6 +45,16 @@ } } +# built-ins that can always raise exceptions +builtins_exceptions = { + int: [ValueError], + float: [ValueError], + chr: [ValueError], + unichr: [ValueError], + unicode: [UnicodeDecodeError], +} + + def _assert_rpythonic(func): """Raise ValueError if ``func`` is obviously not RPython""" if func.func_doc and func.func_doc.lstrip().startswith('NOT_RPYTHON'): @@ -135,6 +145,11 @@ raise WrapException return Constant(obj) + def exc_wrap(self, exc): + w_value = self.wrap(exc) + w_type = self.wrap(type(exc)) + return FSException(w_type, w_value) + def int_w(self, w_obj): if isinstance(w_obj, Constant): val = w_obj.value @@ -143,15 +158,6 @@ return val return self.unwrap(w_obj) - def uint_w(self, w_obj): - if isinstance(w_obj, Constant): - val = w_obj.value - if type(val) is not rarithmetic.r_uint: - raise TypeError("expected unsigned: " + repr(w_obj)) - return val - return self.unwrap(w_obj) - - def str_w(self, w_obj): if isinstance(w_obj, Constant): val = w_obj.value @@ -160,14 +166,6 @@ return val return self.unwrap(w_obj) - def float_w(self, w_obj): - if isinstance(w_obj, Constant): - val = w_obj.value - if type(val) is not float: - raise TypeError("expected float: " + repr(w_obj)) - return val - return self.unwrap(w_obj) - def unwrap(self, w_obj): if isinstance(w_obj, Variable): raise UnwrapException @@ -176,40 +174,9 @@ else: raise TypeError("not wrapped: " + repr(w_obj)) - def unwrap_for_computation(self, w_obj): - obj = self.unwrap(w_obj) - to_check = obj - if hasattr(to_check, 'im_self'): - to_check = to_check.im_self - if (not isinstance(to_check, (type, types.ClassType, types.ModuleType)) and - # classes/types/modules are assumed immutable - hasattr(to_check, '__class__') and to_check.__class__.__module__ != '__builtin__'): - frozen = hasattr(to_check, '_freeze_') - if frozen: - assert to_check._freeze_() is True - else: - # cannot count on it not mutating at runtime! - raise UnwrapException - return obj - def exception_issubclass_w(self, w_cls1, w_cls2): return self.is_true(self.issubtype(w_cls1, w_cls2)) - def _exception_match(self, w_exc_type, w_check_class): - """Helper for exception_match - - Handles the base case where w_check_class is a constant exception - type. - """ - if self.is_w(w_exc_type, w_check_class): - return True # fast path (also here to handle string exceptions) - try: - return self.exception_issubclass_w(w_exc_type, w_check_class) - except FSException, e: - if e.match(self, self.w_TypeError): # string exceptions maybe - return False - raise - def exception_match(self, w_exc_type, w_check_class): """Checks if the given exception type matches 'w_check_class'.""" try: @@ -221,47 +188,43 @@ "Catching %s is not valid in RPython" % check_class.__name__) if not isinstance(check_class, tuple): # the simple case - return self._exception_match(w_exc_type, w_check_class) + return self.exception_issubclass_w(w_exc_type, w_check_class) # special case for StackOverflow (see rlib/rstackovf.py) if check_class == rstackovf.StackOverflow: w_real_class = self.wrap(rstackovf._StackOverflow) - return self._exception_match(w_exc_type, w_real_class) + return self.exception_issubclass_w(w_exc_type, w_real_class) # checking a tuple of classes for w_klass in self.unpackiterable(w_check_class): if self.exception_match(w_exc_type, w_klass): return True return False - def exc_from_raise(self, w_type, w_value): + def exc_from_raise(self, w_arg1, w_arg2): """ Create a wrapped exception from the arguments of a raise statement. Returns an FSException object whose w_value is an instance of w_type. """ - if self.isinstance_w(w_type, self.w_type): + if self.isinstance_w(w_arg1, self.w_type): # this is for all cases of the form (Class, something) - if self.is_w(w_value, self.w_None): + if self.is_w(w_arg2, self.w_None): # raise Type: we assume we have to instantiate Type - w_value = self.call_function(w_type) - w_type = self.type(w_value) + w_value = self.call_function(w_arg1) else: - w_valuetype = self.type(w_value) - if self.exception_issubclass_w(w_valuetype, w_type): + w_valuetype = self.type(w_arg2) + if self.exception_issubclass_w(w_valuetype, w_arg1): # raise Type, Instance: let etype be the exact type of value - w_type = w_valuetype + w_value = w_arg2 else: # raise Type, X: assume X is the constructor argument - w_value = self.call_function(w_type, w_value) - w_type = self.type(w_value) + w_value = self.call_function(w_arg1, w_arg2) else: # the only case left here is (inst, None), from a 'raise inst'. - w_inst = w_type - w_instclass = self.type(w_inst) - if not self.is_w(w_value, self.w_None): - raise FSException(self.w_TypeError, self.wrap( + if not self.is_w(w_arg2, self.w_None): + raise self.exc_wrap(TypeError( "instance exception may not have a separate value")) - w_value = w_inst - w_type = w_instclass + w_value = w_arg1 + w_type = self.type(w_value) return FSException(w_type, w_value) def unpackiterable(self, w_iterable): @@ -291,12 +254,8 @@ return self.wrap(not self.is_true(w_obj)) def is_true(self, w_obj): - try: - obj = self.unwrap_for_computation(w_obj) - except UnwrapException: - pass - else: - return bool(obj) + if w_obj.foldable(): + return bool(w_obj.value) w_truthvalue = self.frame.do_operation('is_true', w_obj) return self.frame.guessbool(w_truthvalue) @@ -316,7 +275,7 @@ try: v, next_unroller = it.step() except IndexError: - raise FSException(self.w_StopIteration, self.w_None) + raise self.exc_wrap(StopIteration()) else: frame.replace_in_stack(it, next_unroller) return self.wrap(v) @@ -324,16 +283,6 @@ frame.handle_implicit_exceptions([StopIteration, RuntimeError]) return w_item - def setitem(self, w_obj, w_key, w_val): - # protect us from globals write access - if w_obj is self.frame.w_globals: - raise FlowingError(self.frame, - "Attempting to modify global variable %r." % (w_key)) - return self.frame.do_operation_with_implicit_exceptions('setitem', - w_obj, w_key, w_val) - - def setitem_str(self, w_obj, key, w_value): - return self.setitem(w_obj, self.wrap(key), w_value) def getattr(self, w_obj, w_name): # handling special things like sys @@ -343,12 +292,8 @@ if w_name not in const_w: return self.frame.do_operation_with_implicit_exceptions('getattr', w_obj, w_name) - try: - obj = self.unwrap_for_computation(w_obj) - name = self.unwrap_for_computation(w_name) - except UnwrapException: - pass - else: + if w_obj.foldable() and w_name.foldable(): + obj, name = w_obj.value, w_name.value try: result = getattr(obj, name) except Exception, e: @@ -369,8 +314,8 @@ def import_name(self, name, glob=None, loc=None, frm=None, level=-1): try: mod = __import__(name, glob, loc, frm, level) - except ImportError, e: - raise FSException(self.w_ImportError, self.wrap(str(e))) + except ImportError as e: + raise self.exc_wrap(e) return self.wrap(mod) def import_from(self, w_module, w_name): @@ -378,15 +323,15 @@ assert isinstance(w_name, Constant) # handle sys if w_module in self.not_really_const: - const_w = self.not_really_const[w_obj] + const_w = self.not_really_const[w_module] if w_name not in const_w: return self.frame.do_operation_with_implicit_exceptions('getattr', - w_obj, w_name) + w_module, w_name) try: return self.wrap(getattr(w_module.value, w_name.value)) except AttributeError: - raise FSException(self.w_ImportError, - self.wrap("cannot import name '%s'" % w_name.value)) + raise self.exc_wrap(ImportError( + "cannot import name '%s'" % w_name.value)) def call_method(self, w_obj, methname, *arg_w): w_meth = self.getattr(w_obj, self.wrap(methname)) @@ -417,7 +362,7 @@ args_w = args.arguments_w + self.unpackiterable(args.w_stararg) else: args_w = args.arguments_w - return sc(self, fn, args_w) + return sc(self, args_w) if args.keywords or isinstance(args.w_stararg, Variable): shape, args_w = args.flatten() @@ -430,15 +375,6 @@ args_w = args.arguments_w w_res = self.frame.do_operation('simple_call', w_callable, *args_w) - # maybe the call has generated an exception (any one) - # but, let's say, not if we are calling a built-in class or function - # because this gets in the way of the special-casing of - # - # raise SomeError(x) - # - # as shown by test_objspace.test_raise3. - - exceptions = [Exception] # *any* exception by default if isinstance(w_callable, Constant): c = w_callable.value if (isinstance(c, (types.BuiltinFunctionType, @@ -446,8 +382,11 @@ types.ClassType, types.TypeType)) and c.__module__ in ['__builtin__', 'exceptions']): - exceptions = operation.implicit_exceptions.get(c) - self.frame.handle_implicit_exceptions(exceptions) + if c in builtins_exceptions: + self.frame.handle_implicit_exceptions(builtins_exceptions[c]) + return w_res + # *any* exception for non-builtins + self.frame.handle_implicit_exceptions([Exception]) return w_res def find_global(self, w_globals, varname): @@ -462,82 +401,61 @@ raise FlowingError(self.frame, self.wrap(message)) return self.wrap(value) -def make_op(name, arity): +def make_impure_op(oper): + def generic_operator(self, *args_w): + if len(args_w) != oper.arity: + raise TypeError(oper.name + " got the wrong number of arguments") + w_result = self.frame.do_operation_with_implicit_exceptions(oper.name, *args_w) + return w_result + return generic_operator + +def make_op(oper): """Add function operation to the flow space.""" - if getattr(FlowObjSpace, name, None) is not None: - return - - op = None - skip = False - arithmetic = False - - if (name.startswith('del') or - name.startswith('set') or - name.startswith('inplace_')): - # skip potential mutators - skip = True - elif name in ('id', 'hash', 'iter', 'userdel'): - # skip potential runtime context dependecies - skip = True - elif name in ('repr', 'str'): - rep = getattr(__builtin__, name) - def op(obj): - s = rep(obj) - if "at 0x" in s: - print >>sys.stderr, "Warning: captured address may be awkward" - return s - else: - op = operation.FunctionByName[name] - arithmetic = (name + '_ovf') in operation.FunctionByName - - if not op and not skip: - raise ValueError("XXX missing operator: %s" % (name,)) + name = oper.name + func = oper.pyfunc def generic_operator(self, *args_w): - assert len(args_w) == arity, name + " got the wrong number of arguments" - if op: - args = [] - for w_arg in args_w: - try: - arg = self.unwrap_for_computation(w_arg) - except UnwrapException: - break + assert len(args_w) == oper.arity, name + " got the wrong number of arguments" + args = [] + if all(w_arg.foldable() for w_arg in args_w): + args = [w_arg.value for w_arg in args_w] + # All arguments are constants: call the operator now + try: + result = func(*args) + except Exception, e: + etype = e.__class__ + msg = "%s%r always raises %s: %s" % ( + name, tuple(args), etype, e) + raise FlowingError(self.frame, msg) + else: + # don't try to constant-fold operations giving a 'long' + # result. The result is probably meant to be sent to + # an intmask(), but the 'long' constant confuses the + # annotator a lot. + if oper.can_overflow and type(result) is long: + pass + # don't constant-fold getslice on lists, either + elif name == 'getslice' and type(result) is list: + pass + # otherwise, fine else: - args.append(arg) - else: - # All arguments are constants: call the operator now - try: - result = op(*args) - except Exception, e: - etype = e.__class__ - msg = "%s%r always raises %s: %s" % ( - name, tuple(args), etype, e) - raise FlowingError(self.frame, msg) - else: - # don't try to constant-fold operations giving a 'long' - # result. The result is probably meant to be sent to - # an intmask(), but the 'long' constant confuses the - # annotator a lot. - if arithmetic and type(result) is long: + try: + return self.wrap(result) + except WrapException: + # type cannot sanely appear in flow graph, + # store operation with variable result instead pass - # don't constant-fold getslice on lists, either - elif name == 'getslice' and type(result) is list: - pass - # otherwise, fine - else: - try: - return self.wrap(result) - except WrapException: - # type cannot sanely appear in flow graph, - # store operation with variable result instead - pass w_result = self.frame.do_operation_with_implicit_exceptions(name, *args_w) return w_result + return generic_operator - setattr(FlowObjSpace, name, generic_operator) - -for (name, symbol, arity, specialnames) in operation.MethodTable: - make_op(name, arity) +for oper in operation.op.__dict__.values(): + if getattr(FlowObjSpace, oper.name, None) is None: + if oper.pure: + op_method = make_op(oper) + else: + op_method = make_impure_op(oper) + setattr(FlowObjSpace, oper.name, op_method) def build_flow(func, space=FlowObjSpace()): diff --git a/rpython/flowspace/operation.py b/rpython/flowspace/operation.py --- a/rpython/flowspace/operation.py +++ b/rpython/flowspace/operation.py @@ -8,93 +8,53 @@ import operator from rpython.tool.sourcetools import compile2 from rpython.rlib.rarithmetic import ovfcheck +from rpython.flowspace.model import Constant -# this is a copy that should be shared with standard objspace +class _OpHolder(object): pass +op = _OpHolder() -MethodTable = [ -# method name # symbol # number of arguments # special method name(s) - ('is_', 'is', 2, []), - ('id', 'id', 1, []), - ('type', 'type', 1, []), - ('isinstance', 'isinstance', 2, ['__instancecheck__']), - ('issubtype', 'issubtype', 2, ['__subclasscheck__']), # not for old-style classes - ('repr', 'repr', 1, ['__repr__']), - ('str', 'str', 1, ['__str__']), - ('format', 'format', 2, ['__format__']), - ('len', 'len', 1, ['__len__']), - ('hash', 'hash', 1, ['__hash__']), - ('getattr', 'getattr', 2, ['__getattribute__']), - ('setattr', 'setattr', 3, ['__setattr__']), - ('delattr', 'delattr', 2, ['__delattr__']), - ('getitem', 'getitem', 2, ['__getitem__']), - ('setitem', 'setitem', 3, ['__setitem__']), - ('delitem', 'delitem', 2, ['__delitem__']), - ('getslice', 'getslice', 3, ['__getslice__']), - ('setslice', 'setslice', 4, ['__setslice__']), - ('delslice', 'delslice', 3, ['__delslice__']), - ('trunc', 'trunc', 1, ['__trunc__']), - ('pos', 'pos', 1, ['__pos__']), - ('neg', 'neg', 1, ['__neg__']), - ('nonzero', 'truth', 1, ['__nonzero__']), - ('abs' , 'abs', 1, ['__abs__']), - ('hex', 'hex', 1, ['__hex__']), - ('oct', 'oct', 1, ['__oct__']), - ('ord', 'ord', 1, []), - ('invert', '~', 1, ['__invert__']), - ('add', '+', 2, ['__add__', '__radd__']), - ('sub', '-', 2, ['__sub__', '__rsub__']), - ('mul', '*', 2, ['__mul__', '__rmul__']), - ('truediv', '/', 2, ['__truediv__', '__rtruediv__']), - ('floordiv', '//', 2, ['__floordiv__', '__rfloordiv__']), - ('div', 'div', 2, ['__div__', '__rdiv__']), - ('mod', '%', 2, ['__mod__', '__rmod__']), - ('divmod', 'divmod', 2, ['__divmod__', '__rdivmod__']), - ('pow', '**', 3, ['__pow__', '__rpow__']), - ('lshift', '<<', 2, ['__lshift__', '__rlshift__']), - ('rshift', '>>', 2, ['__rshift__', '__rrshift__']), - ('and_', '&', 2, ['__and__', '__rand__']), - ('or_', '|', 2, ['__or__', '__ror__']), - ('xor', '^', 2, ['__xor__', '__rxor__']), - ('int', 'int', 1, ['__int__']), - ('index', 'index', 1, ['__index__']), - ('float', 'float', 1, ['__float__']), - ('long', 'long', 1, ['__long__']), - ('inplace_add', '+=', 2, ['__iadd__']), - ('inplace_sub', '-=', 2, ['__isub__']), - ('inplace_mul', '*=', 2, ['__imul__']), - ('inplace_truediv', '/=', 2, ['__itruediv__']), - ('inplace_floordiv','//=', 2, ['__ifloordiv__']), - ('inplace_div', 'div=', 2, ['__idiv__']), - ('inplace_mod', '%=', 2, ['__imod__']), - ('inplace_pow', '**=', 2, ['__ipow__']), - ('inplace_lshift', '<<=', 2, ['__ilshift__']), - ('inplace_rshift', '>>=', 2, ['__irshift__']), - ('inplace_and', '&=', 2, ['__iand__']), - ('inplace_or', '|=', 2, ['__ior__']), - ('inplace_xor', '^=', 2, ['__ixor__']), - ('lt', '<', 2, ['__lt__', '__gt__']), - ('le', '<=', 2, ['__le__', '__ge__']), - ('eq', '==', 2, ['__eq__', '__eq__']), - ('ne', '!=', 2, ['__ne__', '__ne__']), - ('gt', '>', 2, ['__gt__', '__lt__']), - ('ge', '>=', 2, ['__ge__', '__le__']), - ('cmp', 'cmp', 2, ['__cmp__']), # rich cmps preferred - ('coerce', 'coerce', 2, ['__coerce__', '__coerce__']), - ('contains', 'contains', 2, ['__contains__']), - ('iter', 'iter', 1, ['__iter__']), - ('next', 'next', 1, ['next']), -# ('call', 'call', 3, ['__call__']), - ('get', 'get', 3, ['__get__']), - ('set', 'set', 3, ['__set__']), - ('delete', 'delete', 2, ['__delete__']), - ('userdel', 'del', 1, ['__del__']), - ('buffer', 'buffer', 1, ['__buffer__']), # see buffer.py - ] +func2op = {} +class SpaceOperator(object): + def __init__(self, name, arity, symbol, pyfunc, pure=False, + can_overflow=False): + self.name = name + self.arity = arity + self.symbol = symbol + self.pyfunc = pyfunc + self.pure = pure + self.can_overflow = can_overflow + self.canraise = [] -FunctionByName = {} # dict {"operation_name": <built-in function>} -OperationName = {} # dict {<built-in function>: "operation_name"} -Arity = {} # dict {"operation name": number of arguments} + def make_sc(self): + def sc_operator(space, args_w): + if len(args_w) != self.arity: + if self is op.pow and len(args_w) == 2: + args_w = args_w + [Constant(None)] + elif self is op.getattr and len(args_w) == 3: + return space.frame.do_operation('simple_call', Constant(getattr), *args_w) + else: + raise Exception("should call %r with exactly %d arguments" % ( + self.name, self.arity)) + # completely replace the call with the underlying + # operation and its limited implicit exceptions semantic + return getattr(space, self.name)(*args_w) + return sc_operator + + +def add_operator(name, arity, symbol, pyfunc=None, pure=False, ovf=False): + operator_func = getattr(operator, name, None) + oper = SpaceOperator(name, arity, symbol, pyfunc, pure, can_overflow=ovf) + setattr(op, name, oper) + if pyfunc is not None: + func2op[pyfunc] = oper + if operator_func: + func2op[operator_func] = oper + if pyfunc is None: + oper.pyfunc = operator_func + if ovf: + ovf_func = lambda *args: ovfcheck(oper.pyfunc(*args)) + add_operator(name + '_ovf', arity, symbol, pyfunc=ovf_func) # ____________________________________________________________ @@ -186,33 +146,6 @@ def userdel(x): x.__del__() -def neg_ovf(x): - return ovfcheck(-x) - -def abs_ovf(x): - return ovfcheck(abs(x)) - -def add_ovf(x, y): - return ovfcheck(x + y) - -def sub_ovf(x, y): - return ovfcheck(x - y) - -def mul_ovf(x, y): - return ovfcheck(x * y) - -def floordiv_ovf(x, y): - return ovfcheck(operator.floordiv(x, y)) - -def div_ovf(x, y): - return ovfcheck(operator.div(x, y)) - -def mod_ovf(x, y): - return ovfcheck(x % y) - -def lshift_ovf(x, y): - return ovfcheck(x << y) - # slicing: operator.{get,set,del}slice() don't support b=None or c=None def do_getslice(a, b, c): return a[b:c] @@ -226,101 +159,91 @@ def unsupported(*args): raise ValueError("this is not supported") -# ____________________________________________________________ -# The following table can list several times the same operation name, -# if multiple built-in functions correspond to it. The first one should -# be picked, though, as the best built-in for the given operation name. -# Lines ('name', operator.name) are added automatically. +add_operator('is_', 2, 'is', pure=True) +add_operator('id', 1, 'id', pyfunc=id) +add_operator('type', 1, 'type', pyfunc=new_style_type, pure=True) +add_operator('isinstance', 2, 'isinstance', pyfunc=isinstance, pure=True) +add_operator('issubtype', 2, 'issubtype', pyfunc=issubclass, pure=True) # not for old-style classes +add_operator('repr', 1, 'repr', pyfunc=repr, pure=True) +add_operator('str', 1, 'str', pyfunc=str, pure=True) +add_operator('format', 2, 'format', pyfunc=unsupported) +add_operator('len', 1, 'len', pyfunc=len, pure=True) +add_operator('hash', 1, 'hash', pyfunc=hash) +add_operator('getattr', 2, 'getattr', pyfunc=getattr, pure=True) +add_operator('setattr', 3, 'setattr', pyfunc=setattr) +add_operator('delattr', 2, 'delattr', pyfunc=delattr) +add_operator('getitem', 2, 'getitem', pure=True) +add_operator('setitem', 3, 'setitem') +add_operator('delitem', 2, 'delitem') +add_operator('getslice', 3, 'getslice', pyfunc=do_getslice, pure=True) +add_operator('setslice', 4, 'setslice', pyfunc=do_setslice) +add_operator('delslice', 3, 'delslice', pyfunc=do_delslice) +add_operator('trunc', 1, 'trunc', pyfunc=unsupported) +add_operator('pos', 1, 'pos', pure=True) +add_operator('neg', 1, 'neg', pure=True, ovf=True) +add_operator('nonzero', 1, 'truth', pyfunc=bool, pure=True) +op.is_true = op.nonzero +add_operator('abs' , 1, 'abs', pyfunc=abs, pure=True, ovf=True) +add_operator('hex', 1, 'hex', pyfunc=hex, pure=True) +add_operator('oct', 1, 'oct', pyfunc=oct, pure=True) +add_operator('ord', 1, 'ord', pyfunc=ord, pure=True) +add_operator('invert', 1, '~', pure=True) +add_operator('add', 2, '+', pure=True, ovf=True) +add_operator('sub', 2, '-', pure=True, ovf=True) +add_operator('mul', 2, '*', pure=True, ovf=True) +add_operator('truediv', 2, '/', pure=True) +add_operator('floordiv', 2, '//', pure=True, ovf=True) +add_operator('div', 2, 'div', pure=True, ovf=True) +add_operator('mod', 2, '%', pure=True, ovf=True) +add_operator('divmod', 2, 'divmod', pyfunc=divmod, pure=True) +add_operator('pow', 3, '**', pyfunc=pow, pure=True) +add_operator('lshift', 2, '<<', pure=True, ovf=True) +add_operator('rshift', 2, '>>', pure=True) +add_operator('and_', 2, '&', pure=True) +add_operator('or_', 2, '|', pure=True) +add_operator('xor', 2, '^', pure=True) +add_operator('int', 1, 'int', pyfunc=do_int, pure=True) +add_operator('index', 1, 'index', pyfunc=do_index, pure=True) +add_operator('float', 1, 'float', pyfunc=do_float, pure=True) +add_operator('long', 1, 'long', pyfunc=do_long, pure=True) +add_operator('inplace_add', 2, '+=', pyfunc=inplace_add) +add_operator('inplace_sub', 2, '-=', pyfunc=inplace_sub) +add_operator('inplace_mul', 2, '*=', pyfunc=inplace_mul) +add_operator('inplace_truediv', 2, '/=', pyfunc=inplace_truediv) +add_operator('inplace_floordiv', 2, '//=', pyfunc=inplace_floordiv) +add_operator('inplace_div', 2, 'div=', pyfunc=inplace_div) +add_operator('inplace_mod', 2, '%=', pyfunc=inplace_mod) +add_operator('inplace_pow', 2, '**=', pyfunc=inplace_pow) +add_operator('inplace_lshift', 2, '<<=', pyfunc=inplace_lshift) +add_operator('inplace_rshift', 2, '>>=', pyfunc=inplace_rshift) +add_operator('inplace_and', 2, '&=', pyfunc=inplace_and) +add_operator('inplace_or', 2, '|=', pyfunc=inplace_or) +add_operator('inplace_xor', 2, '^=', pyfunc=inplace_xor) +add_operator('lt', 2, '<', pure=True) +add_operator('le', 2, '<=', pure=True) +add_operator('eq', 2, '==', pure=True) +add_operator('ne', 2, '!=', pure=True) +add_operator('gt', 2, '>', pure=True) +add_operator('ge', 2, '>=', pure=True) +add_operator('cmp', 2, 'cmp', pyfunc=cmp, pure=True) # rich cmps preferred +add_operator('coerce', 2, 'coerce', pyfunc=coerce, pure=True) +add_operator('contains', 2, 'contains', pure=True) +add_operator('iter', 1, 'iter', pyfunc=iter) +add_operator('next', 1, 'next', pyfunc=next) +#add_operator('call', 3, 'call') +add_operator('get', 3, 'get', pyfunc=get, pure=True) +add_operator('set', 3, 'set', pyfunc=set) +add_operator('delete', 2, 'delete', pyfunc=delete) +add_operator('userdel', 1, 'del', pyfunc=userdel) +add_operator('buffer', 1, 'buffer', pyfunc=buffer, pure=True) # see buffer.py -# INTERNAL ONLY, use the dicts declared at the top of the file. -Table = [ - ('id', id), - ('type', new_style_type), - ('type', type), - ('isinstance', isinstance), - ('issubtype', issubclass), - ('repr', repr), - ('str', str), - ('format', unsupported), - ('len', len), - ('hash', hash), - ('getattr', getattr), - ('setattr', setattr), - ('delattr', delattr), - ('nonzero', bool), - ('nonzero', operator.truth), - ('is_true', bool), - ('is_true', operator.truth), - ('trunc', unsupported), - ('abs' , abs), - ('hex', hex), - ('oct', oct), - ('ord', ord), - ('divmod', divmod), - ('pow', pow), - ('int', do_int), - ('index', do_index), - ('float', do_float), - ('long', do_long), - ('inplace_add', inplace_add), - ('inplace_sub', inplace_sub), - ('inplace_mul', inplace_mul), - ('inplace_truediv', inplace_truediv), - ('inplace_floordiv',inplace_floordiv), - ('inplace_div', inplace_div), - ('inplace_mod', inplace_mod), - ('inplace_pow', inplace_pow), - ('inplace_lshift', inplace_lshift), - ('inplace_rshift', inplace_rshift), - ('inplace_and', inplace_and), - ('inplace_or', inplace_or), - ('inplace_xor', inplace_xor), - ('cmp', cmp), - ('coerce', coerce), - ('iter', iter), - ('next', next), - ('get', get), - ('set', set), - ('delete', delete), - ('userdel', userdel), - ('buffer', buffer), - ('getslice', do_getslice), - ('setslice', do_setslice), - ('delslice', do_delslice), - # --- operations added by graph transformations --- - ('neg_ovf', neg_ovf), - ('abs_ovf', abs_ovf), - ('add_ovf', add_ovf), - ('sub_ovf', sub_ovf), - ('mul_ovf', mul_ovf), - ('floordiv_ovf', floordiv_ovf), - ('div_ovf', div_ovf), - ('mod_ovf', mod_ovf), - ('lshift_ovf', lshift_ovf), -] +# Other functions that get directly translated to SpaceOperators +func2op[type] = op.type +func2op[operator.truth] = op.nonzero if hasattr(__builtin__, 'next'): - Table.append(('next', __builtin__.next)) + func2op[__builtin__.next] = op.next -def setup(): - # insert all operators - for line in MethodTable: - name = line[0] - if hasattr(operator, name): - Table.append((name, getattr(operator, name))) - # build the dictionaries - for name, func in Table: - if name not in FunctionByName: - FunctionByName[name] = func - if func not in OperationName: - OperationName[func] = name - # check that the result is complete - for line in MethodTable: - name = line[0] - Arity[name] = line[2] - assert name in FunctionByName -setup() -del Table, setup # INTERNAL ONLY, use the dicts declared at the top of the file op_appendices = { OverflowError: 'ovf', @@ -330,24 +253,18 @@ ValueError: 'val', } -implicit_exceptions = { - int: [ValueError], # built-ins that can always raise exceptions - float: [ValueError], - chr: [ValueError], - unichr: [ValueError], - unicode: [UnicodeDecodeError], - # specifying IndexError, and KeyError beyond Exception, - # allows the annotator to be more precise, see test_reraiseAnything/KeyError in - # the annotator tests - 'getitem': [IndexError, KeyError, Exception], - 'setitem': [IndexError, KeyError, Exception], - 'delitem': [IndexError, KeyError, Exception], - 'contains': [Exception], # from an r_dict - } +# specifying IndexError, and KeyError beyond Exception, +# allows the annotator to be more precise, see test_reraiseAnything/KeyError in +# the annotator tests +op.getitem.canraise = [IndexError, KeyError, Exception] +op.setitem.canraise = [IndexError, KeyError, Exception] +op.delitem.canraise = [IndexError, KeyError, Exception] +op.contains.canraise = [Exception] # from an r_dict def _add_exceptions(names, exc): for name in names.split(): - lis = implicit_exceptions.setdefault(name, []) + oper = getattr(op, name) + lis = oper.canraise if exc in lis: raise ValueError, "your list is causing duplication!" lis.append(exc) @@ -356,12 +273,13 @@ def _add_except_ovf(names): # duplicate exceptions and add OverflowError for name in names.split(): - lis = implicit_exceptions.setdefault(name, [])[:] - lis.append(OverflowError) - implicit_exceptions[name+"_ovf"] = lis + oper = getattr(op, name) + oper_ovf = getattr(op, name+'_ovf') + oper_ovf.canraise = list(oper.canraise) + oper_ovf.canraise.append(OverflowError) _add_exceptions("""div mod divmod truediv floordiv pow - inplace_div inplace_mod inplace_divmod inplace_truediv + inplace_div inplace_mod inplace_truediv inplace_floordiv inplace_pow""", ZeroDivisionError) _add_exceptions("""pow inplace_pow lshift inplace_lshift rshift inplace_rshift""", ValueError) @@ -370,7 +288,7 @@ inplace_floordiv inplace_div inplace_mod inplace_pow inplace_lshift""", OverflowError) # without a _ovf version _add_except_ovf("""neg abs add sub mul - floordiv div mod pow lshift""") # with a _ovf version + floordiv div mod lshift""") # with a _ovf version _add_exceptions("""pow""", OverflowError) # for the float case del _add_exceptions, _add_except_ovf diff --git a/rpython/flowspace/specialcase.py b/rpython/flowspace/specialcase.py --- a/rpython/flowspace/specialcase.py +++ b/rpython/flowspace/specialcase.py @@ -1,27 +1,13 @@ from rpython.flowspace.model import Constant -from rpython.flowspace.operation import OperationName, Arity +from rpython.flowspace.operation import func2op, op from rpython.rlib.rarithmetic import r_uint from rpython.rlib.objectmodel import we_are_translated -def sc_import(space, fn, args_w): +def sc_import(space, args_w): assert len(args_w) > 0 and len(args_w) <= 5, 'import needs 1 to 5 arguments' args = [space.unwrap(arg) for arg in args_w] return space.import_name(*args) -def sc_operator(space, fn, args_w): - opname = OperationName[fn] - if len(args_w) != Arity[opname]: - if opname == 'pow' and len(args_w) == 2: - args_w = args_w + [Constant(None)] - elif opname == 'getattr' and len(args_w) == 3: - return space.frame.do_operation('simple_call', Constant(getattr), *args_w) - else: - raise Exception("should call %r with exactly %d arguments" % ( - fn, Arity[opname])) - # completely replace the call with the underlying - # operation and its limited implicit exceptions semantic - return getattr(space, opname)(*args_w) - # _________________________________________________________________________ # a simplified version of the basic printing routines, for RPython programs class StdOutBuffer: @@ -47,7 +33,7 @@ # _________________________________________________________________________ -def sc_r_uint(space, r_uint, args_w): +def sc_r_uint(space, args_w): # special case to constant-fold r_uint(32-bit-constant) # (normally, the 32-bit constant is a long, and is not allowed to # show up in the flow graphs at all) @@ -56,10 +42,10 @@ return Constant(r_uint(w_value.value)) return space.frame.do_operation('simple_call', space.wrap(r_uint), w_value) -def sc_we_are_translated(space, we_are_translated, args_w): +def sc_we_are_translated(space, args_w): return Constant(True) -def sc_locals(space, locals, args): +def sc_locals(space, args): raise Exception( "A function calling locals() is not RPython. " "Note that if you're translating code outside the PyPy " @@ -71,5 +57,5 @@ SPECIAL_CASES = {__import__: sc_import, r_uint: sc_r_uint, we_are_translated: sc_we_are_translated, locals: sc_locals} -for fn in OperationName: - SPECIAL_CASES[fn] = sc_operator +for fn, oper in func2op.items(): + SPECIAL_CASES[fn] = oper.make_sc() diff --git a/rpython/flowspace/test/test_objspace.py b/rpython/flowspace/test/test_objspace.py --- a/rpython/flowspace/test/test_objspace.py +++ b/rpython/flowspace/test/test_objspace.py @@ -1,6 +1,6 @@ from __future__ import with_statement import new -import py, sys +import py from contextlib import contextmanager from rpython.flowspace.model import Constant, mkentrymap, c_last_exception @@ -1169,6 +1169,28 @@ 'iter': 1, 'newlist': 1, 'next': 1, 'simple_call': 1} + def test_mutate_const_list(self): + lst = list('abcdef') + def f(): + lst[0] = 'x' + return lst + graph = self.codetest(f) + assert 'setitem' in self.all_operations(graph) + + def test_sys_getattr(self): + def f(): + import sys + return sys.modules + graph = self.codetest(f) + assert 'getattr' in self.all_operations(graph) + + def test_sys_import_from(self): + def f(): + from sys import modules + return modules + graph = self.codetest(f) + assert 'getattr' in self.all_operations(graph) + DATA = {'x': 5, 'y': 6} diff --git a/rpython/rtyper/lltypesystem/opimpl.py b/rpython/rtyper/lltypesystem/opimpl.py --- a/rpython/rtyper/lltypesystem/opimpl.py +++ b/rpython/rtyper/lltypesystem/opimpl.py @@ -1,4 +1,4 @@ -from rpython.flowspace.operation import FunctionByName +from rpython.flowspace.operation import op from rpython.rlib import debug from rpython.rlib.rarithmetic import is_valid_int from rpython.rtyper.lltypesystem import lltype, llmemory @@ -13,7 +13,6 @@ 'lt': True, 'le': True, 'eq': True, 'ne': True, 'is_true': True} -ops_unary = {'is_true': True, 'neg': True, 'abs': True, 'invert': True} # global synonyms for some types from rpython.rlib.rarithmetic import intmask @@ -46,11 +45,13 @@ def get_primitive_op_src(fullopname): assert '_' in fullopname, "%s: not a primitive op" % (fullopname,) typname, opname = fullopname.split('_', 1) - if opname not in FunctionByName and (opname + '_') in FunctionByName: - func = FunctionByName[opname + '_'] # or_, and_ + if hasattr(op, opname): + oper = getattr(op, opname) + elif hasattr(op, opname + '_'): + oper = getattr(op, opname + '_') # or_, and_ else: - assert opname in FunctionByName, "%s: not a primitive op" % (fullopname,) - func = FunctionByName[opname] + raise ValueError("%s: not a primitive op" % (fullopname,)) + func = oper.pyfunc if typname == 'char': # char_lt, char_eq, ... @@ -72,7 +73,7 @@ fullopname,) argtype = argtype_by_name[typname] - if opname in ops_unary: + if oper.arity == 1: def op_function(x): if not isinstance(x, argtype): raise TypeError("%r arg must be %s, got %r instead" % ( diff --git a/rpython/translator/simplify.py b/rpython/translator/simplify.py --- a/rpython/translator/simplify.py +++ b/rpython/translator/simplify.py @@ -120,7 +120,8 @@ covf = Constant(rarithmetic.ovfcheck) def check_syntax(opname): - exlis = operation.implicit_exceptions.get("%s_ovf" % (opname,), []) + oper = getattr(operation.op, opname + "_ovf") + exlis = oper.canraise if OverflowError not in exlis: raise Exception("ovfcheck in %s: Operation %s has no" " overflow variant" % (graph.name, opname)) @@ -495,11 +496,11 @@ # look for removable operations whose result is never used for i in range(len(block.operations)-1, -1, -1): op = block.operations[i] - if op.result not in read_vars: + if op.result not in read_vars: if canremove(op, block): del block.operations[i] - elif op.opname == 'simple_call': - # XXX we want to have a more effective and safe + elif op.opname == 'simple_call': + # XXX we want to have a more effective and safe # way to check if this operation has side effects # ... if op.args and isinstance(op.args[0], Constant): @@ -626,7 +627,7 @@ while candidates: cand, tgts = candidates.pop() - newexits = list(cand.exits) + newexits = list(cand.exits) for case, tgt in tgts: exit = cand.exits[case] rrenaming = dict(zip(tgt.inputargs,exit.args)) diff --git a/rpython/translator/test/test_translator.py b/rpython/translator/test/test_translator.py --- a/rpython/translator/test/test_translator.py +++ b/rpython/translator/test/test_translator.py @@ -8,7 +8,7 @@ d['key'] = 'value' def test_example(): - t = TranslationContext(simplifying=True) + t = TranslationContext() t.buildflowgraph(example) # this specific example triggered a bug in simplify.py #t.view() diff --git a/rpython/translator/translator.py b/rpython/translator/translator.py --- a/rpython/translator/translator.py +++ b/rpython/translator/translator.py @@ -21,7 +21,6 @@ class TranslationContext(object): FLOWING_FLAGS = { 'verbose': False, - 'simplifying': True, 'list_comprehension_operations': False, # True, - not super-tested } @@ -30,8 +29,7 @@ from rpython.config.translationoption import get_combined_translation_config config = get_combined_translation_config(translating=True) # ZZZ should go away in the end - for attr in ['verbose', 'simplifying', - 'list_comprehension_operations']: + for attr in ['verbose', 'list_comprehension_operations']: if attr in flowing_flags: setattr(config.translation, attr, flowing_flags[attr]) self.config = config @@ -54,8 +52,7 @@ if self.config.translation.verbose: log.start(nice_repr_for_func(func)) graph = build_flow(func) - if self.config.translation.simplifying: - simplify.simplify_graph(graph) + simplify.simplify_graph(graph) if self.config.translation.list_comprehension_operations: simplify.detect_list_comprehension(graph) if self.config.translation.verbose: _______________________________________________ pypy-commit mailing list pypy-commit@python.org http://mail.python.org/mailman/listinfo/pypy-commit